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

pkarwasz pushed a commit to branch feature/main/split-jul
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 0de1b929040f2b92b302fca3aaf04b1cb07f93c6
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Mon Sep 9 20:14:59 2024 +0200

    Remove `CoreLogger` and mode level propagator to new module
---
 {log4j-jul => log4j-jul-propagator}/pom.xml        |  73 +---
 .../log4j/jul/propagator/JulLevelPropagator.java   | 100 ++++++
 .../jul/propagator/JulLevelPropagatorTest.java     |  67 ++++
 .../src/test/resources/log4j2-test.xml             |  31 +-
 .../src/test/resources/logging-test.properties     |  21 ++
 log4j-jul/pom.xml                                  |  14 +
 .../org/apache/logging/log4j/jul/ApiLogger.java    | 369 ++++++++++++---------
 .../org/apache/logging/log4j/jul/CoreLogger.java   |  78 -----
 .../logging/log4j/jul/CoreLoggerAdapter.java       |  41 ---
 .../logging/log4j/jul/Log4jBridgeHandler.java      | 276 ++++++---------
 .../apache/logging/log4j/jul/WrappedLogger.java    |  75 -----
 .../org/apache/logging/log4j/jul/package-info.java |   2 +-
 .../logging/log4j/jul/test/AbstractLoggerTest.java |   9 +-
 .../log4j/jul/test/CallerInformationTest.java      | 139 ++++++--
 .../logging/log4j/jul/test/CoreLoggerTest.java     | 120 -------
 .../log4j/jul/test/CustomLoggerAdapterTest.java    |  69 ++++
 .../log4j/jul/test/Log4jBridgeHandlerTest.java     |  60 +---
 .../src/test/resources/CallerInformationTest.xml   |   4 +-
 .../src/test/resources/log4j2-julBridge-test.xml   |  23 +-
 .../src/test/resources/logging-test.properties     |  15 +-
 pom.xml                                            |   1 +
 src/site/antora/modules/ROOT/pages/log4j-jul.adoc  | 217 +++++++-----
 .../systemproperties/properties-log4j-jul.adoc     |  50 ++-
 23 files changed, 911 insertions(+), 943 deletions(-)

diff --git a/log4j-jul/pom.xml b/log4j-jul-propagator/pom.xml
similarity index 54%
copy from log4j-jul/pom.xml
copy to log4j-jul-propagator/pom.xml
index 888dd58a5c..21ae711405 100644
--- a/log4j-jul/pom.xml
+++ b/log4j-jul-propagator/pom.xml
@@ -24,9 +24,9 @@
     <relativePath>../log4j-parent</relativePath>
   </parent>
 
-  <artifactId>log4j-jul</artifactId>
-  <name>Apache Log4j JUL Adapter</name>
-  <description>The Apache Log4j implementation of 
java.util.logging</description>
+  <artifactId>log4j-jul-propagator</artifactId>
+  <name>Apache Log4j API to JUL bridge: level propagator</name>
+  <description>Propagates Log4j levels from Log4j Core to JUL to improve 
performance.</description>
 
   <dependencies>
 
@@ -37,68 +37,34 @@
 
     <dependency>
       <groupId>org.apache.logging.log4j</groupId>
-      <artifactId>log4j-kit</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.assertj</groupId>
-      <artifactId>assertj-core</artifactId>
-      <scope>test</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>org.hamcrest</groupId>
-      <artifactId>hamcrest</artifactId>
-      <scope>test</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
+      <artifactId>log4j-core</artifactId>
     </dependency>
 
     <dependency>
       <groupId>org.apache.logging.log4j</groupId>
-      <artifactId>log4j-async-logger</artifactId>
-      <scope>test</scope>
+      <artifactId>log4j-jul</artifactId>
     </dependency>
 
     <dependency>
-      <groupId>org.apache.logging.log4j</groupId>
-      <artifactId>log4j-core</artifactId>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
       <scope>test</scope>
     </dependency>
 
     <dependency>
-      <groupId>org.apache.logging.log4j</groupId>
-      <artifactId>log4j-core-test</artifactId>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-api</artifactId>
       <scope>test</scope>
     </dependency>
+
   </dependencies>
 
   <build>
     <plugins>
+
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-plugin</artifactId>
-        <configuration>
-          <systemPropertyVariables>
-            <java.awt.headless>true</java.awt.headless>
-          </systemPropertyVariables>
-          <argLine>-Xms256m -Xmx1024m</argLine>
-          <forkCount>1</forkCount>
-          <reuseForks>false</reuseForks>
-        </configuration>
-        <dependencies>
-          <!-- Manual override of the Surefire provider -->
-          <!-- The `surefire-platform` provider initializes JUL before calling 
our tests -->
-          <dependency>
-            <groupId>org.apache.maven.surefire</groupId>
-            <artifactId>surefire-junit47</artifactId>
-            <version>${surefire.version}</version>
-          </dependency>
-        </dependencies>
         <executions>
           <execution>
             <id>default-test</id>
@@ -110,27 +76,14 @@
               <excludes>
                 <exclude>Log4jBridgeHandlerTest.java</exclude>
               </excludes>
-            </configuration>
-          </execution>
-          <execution>
-            <!-- this test needs some special configuration, thus its own run 
-->
-            <id>bridgeHandler-test</id>
-            <goals>
-              <goal>test</goal>
-            </goals>
-            <phase>test</phase>
-            <configuration>
-              <includes>
-                <include>Log4jBridgeHandlerTest.java</include>
-              </includes>
               <systemPropertyVariables>
-                
<java.util.logging.config.file>src/test/resources/logging-test.properties</java.util.logging.config.file>
-                
<log4j.configuration.location>classpath:log4j2-julBridge-test.xml</log4j.configuration.location>
+                
<java.util.logging.config.file>${project.basedir}/src/test/resources/logging-test.properties</java.util.logging.config.file>
               </systemPropertyVariables>
             </configuration>
           </execution>
         </executions>
       </plugin>
+
     </plugins>
   </build>
 </project>
diff --git 
a/log4j-jul-propagator/src/main/java/org/apache/logging/log4j/jul/propagator/JulLevelPropagator.java
 
b/log4j-jul-propagator/src/main/java/org/apache/logging/log4j/jul/propagator/JulLevelPropagator.java
new file mode 100644
index 0000000000..9008db2a3f
--- /dev/null
+++ 
b/log4j-jul-propagator/src/main/java/org/apache/logging/log4j/jul/propagator/JulLevelPropagator.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.jul.propagator;
+
+import aQute.bnd.annotation.spi.ServiceProvider;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.logging.log4j.jul.LevelTranslator;
+import org.apache.logging.log4j.jul.Log4jBridgeHandler;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * Propagates Log4j Core level to JUL.
+ */
+@ServiceProvider(value = Log4jBridgeHandler.LevelPropagator.class)
+public class JulLevelPropagator implements Log4jBridgeHandler.LevelPropagator, 
Consumer<Configuration> {
+
+    private static final Logger LOGGER = StatusLogger.getLogger();
+
+    private final AtomicInteger installCount = new AtomicInteger();
+    private LoggerContext context;
+    /** Save "hard" references to configured JUL loggers. */
+    private final Set<java.util.logging.Logger> julLoggerRefs = new 
HashSet<>();
+
+    @Override
+    public void start() {
+        if (installCount.getAndIncrement() == 0) {
+            context = LoggerContext.getContext(false);
+            LOGGER.info("Installing Log4j Core to JUL level propagator for 
context `{}`", context);
+            context.addConfigurationStartedListener(this);
+            propagateLogLevels(context.getConfiguration());
+        }
+    }
+
+    @Override
+    public void stop() {
+        if (installCount.decrementAndGet() == 0) {
+            LOGGER.info("Uninstalling Log4j Core to JUL level propagator for 
context `{}`", context);
+            context.removeConfigurationStartedListener(this);
+            context = null;
+            julLoggerRefs.clear();
+        }
+    }
+
+    @Override
+    public void accept(Configuration configuration) {
+        propagateLogLevels(configuration);
+    }
+
+    private void propagateLogLevels(final Configuration configuration) {
+        LOGGER.info("Starting Log4j Core to JUL level propagation for 
configuration `{}`", configuration);
+        // clear or init. saved JUL logger references
+        // JUL loggers have to be explicitly referenced because JUL internally 
uses
+        // weak references so not instantiated loggers may be garbage collected
+        // and their level config gets lost then.
+        julLoggerRefs.clear();
+
+        final Map<String, LoggerConfig> log4jLoggers = 
configuration.getLoggers();
+        for (LoggerConfig loggerConfig : log4jLoggers.values()) {
+            final java.util.logging.Logger julLog =
+                    
java.util.logging.Logger.getLogger(loggerConfig.getName()); // this also fits 
for root = ""
+            final java.util.logging.Level julLevel =
+                    LevelTranslator.toJavaLevel(loggerConfig.getLevel()); // 
loggerConfig.getLevel() never returns null
+            julLog.setLevel(julLevel);
+            julLoggerRefs.add(julLog);
+        }
+        final java.util.logging.LogManager julMgr = 
java.util.logging.LogManager.getLogManager();
+        for (Enumeration<String> en = julMgr.getLoggerNames(); 
en.hasMoreElements(); ) {
+            final java.util.logging.Logger julLog = 
julMgr.getLogger(en.nextElement());
+            if (julLog != null
+                    && julLog.getLevel() != null
+                    && !"".equals(julLog.getName())
+                    && !log4jLoggers.containsKey(julLog.getName())) {
+                julLog.setLevel(null);
+            }
+        }
+    }
+}
diff --git 
a/log4j-jul-propagator/src/test/java/org/apache/logging/log4j/jul/propagator/JulLevelPropagatorTest.java
 
b/log4j-jul-propagator/src/test/java/org/apache/logging/log4j/jul/propagator/JulLevelPropagatorTest.java
new file mode 100644
index 0000000000..d55b8a3f90
--- /dev/null
+++ 
b/log4j-jul-propagator/src/test/java/org/apache/logging/log4j/jul/propagator/JulLevelPropagatorTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.jul.propagator;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.jul.LevelTranslator;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+class JulLevelPropagatorTest {
+
+    @BeforeAll
+    static void setup() {
+        // Ensure that at least one message was sent.
+        Logger.getGlobal().info("Initialize");
+    }
+
+    @AfterEach
+    void cleanup() {
+        
org.apache.logging.log4j.core.LoggerContext.getContext(false).reconfigure();
+    }
+
+    @Test
+    void initial_synchronization_works() {
+        // JUL levels are set from config files and the initial propagation
+        assertThat(Logger.getLogger("").getLevel()).isEqualTo(Level.SEVERE);
+        
assertThat(Logger.getLogger("foo").getLevel()).isEqualTo(Level.WARNING);
+        
assertThat(Logger.getLogger("foo.bar").getLevel()).isEqualTo(Level.INFO);
+    }
+
+    @Test
+    void synchronization_retained_after_GC() {
+        initial_synchronization_works();
+        System.gc(); // a single call is sufficient
+        initial_synchronization_works();
+    }
+
+    @Test
+    void when_set_level_synchronization_works() {
+        Configurator.setLevel("", LevelTranslator.CONFIG);
+        Configurator.setLevel("foo", org.apache.logging.log4j.Level.DEBUG);
+        Configurator.setLevel("foo.bar", org.apache.logging.log4j.Level.TRACE);
+
+        assertThat(Logger.getLogger("").getLevel()).isEqualTo(Level.CONFIG);
+        assertThat(Logger.getLogger("foo").getLevel()).isEqualTo(Level.FINE);
+        
assertThat(Logger.getLogger("foo.bar").getLevel()).isEqualTo(Level.FINER);
+    }
+}
diff --git a/log4j-jul/src/test/resources/CallerInformationTest.xml 
b/log4j-jul-propagator/src/test/resources/log4j2-test.xml
similarity index 52%
copy from log4j-jul/src/test/resources/CallerInformationTest.xml
copy to log4j-jul-propagator/src/test/resources/log4j2-test.xml
index 4a73dddcc7..092fb50375 100644
--- a/log4j-jul/src/test/resources/CallerInformationTest.xml
+++ b/log4j-jul-propagator/src/test/resources/log4j2-test.xml
@@ -15,28 +15,11 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<Configuration xmlns="https://logging.apache.org/xml/ns";
-               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
-               xsi:schemaLocation="
-                   https://logging.apache.org/xml/ns
-                   https://logging.apache.org/xml/ns/log4j-config-3.xsd";>
-  <Appenders>
-    <List name="Class">
-      <PatternLayout pattern="%class"
-                     alwaysWriteExceptions="false"/>
-    </List>
-    <List name="Method">
-      <PatternLayout pattern="%method"
-                     alwaysWriteExceptions="false"/>
-    </List>
-  </Appenders>
-  <Loggers>
-    <Logger name="ClassLogger" level="INFO">
-      <AppenderRef ref="Class"/>
-    </Logger>
-    <Logger name="MethodLogger" level="INFO">
-      <AppenderRef ref="Method"/>
-    </Logger>
-    <Root level="OFF"/>
-  </Loggers>
+<Configuration xmlns="https://logging.apache.org/xml/ns";>
+    <Appenders/>
+    <Loggers>
+        <Root level="ERROR"/>
+        <Logger name="foo" level="WARN"/>
+        <Logger name="foo.bar" level="INFO"/>
+    </Loggers>
 </Configuration>
diff --git a/log4j-jul-propagator/src/test/resources/logging-test.properties 
b/log4j-jul-propagator/src/test/resources/logging-test.properties
new file mode 100644
index 0000000000..ac2233e9a7
--- /dev/null
+++ b/log4j-jul-propagator/src/test/resources/logging-test.properties
@@ -0,0 +1,21 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+### JUL configuration for JulLevelPropagatorTest
+# Install the bridge
+handlers=org.apache.logging.log4j.jul.Log4jBridgeHandler
+# Propagate levels
+org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels=true
diff --git a/log4j-jul/pom.xml b/log4j-jul/pom.xml
index 888dd58a5c..a8ff7c6946 100644
--- a/log4j-jul/pom.xml
+++ b/log4j-jul/pom.xml
@@ -28,6 +28,17 @@
   <name>Apache Log4j JUL Adapter</name>
   <description>The Apache Log4j implementation of 
java.util.logging</description>
 
+  <properties>
+    <bnd-extra-package-options>
+      <!-- Optional annotations -->
+      org.jspecify.*;resolution:=optional
+    </bnd-extra-package-options>
+    <bnd-extra-module-options>
+      <!-- Optional modules can not be `transitive` -->
+      org.jspecify;transitive=false
+    </bnd-extra-module-options>
+  </properties>
+
   <dependencies>
 
     <dependency>
@@ -110,6 +121,9 @@
               <excludes>
                 <exclude>Log4jBridgeHandlerTest.java</exclude>
               </excludes>
+              <systemPropertyVariables>
+                
<java.util.logging.manager>org.apache.logging.log4j.jul.LogManager</java.util.logging.manager>
+              </systemPropertyVariables>
             </configuration>
           </execution>
           <execution>
diff --git 
a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/ApiLogger.java 
b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/ApiLogger.java
index fc4d4d38a3..21ded344c1 100644
--- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/ApiLogger.java
+++ b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/ApiLogger.java
@@ -18,15 +18,20 @@ package org.apache.logging.log4j.jul;
 
 import static org.apache.logging.log4j.jul.LevelTranslator.toLevel;
 
+import java.text.MessageFormat;
 import java.util.ResourceBundle;
 import java.util.function.Supplier;
 import java.util.logging.Filter;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import java.util.logging.Logger;
+import org.apache.logging.log4j.BridgeAware;
+import org.apache.logging.log4j.LogBuilder;
+import org.apache.logging.log4j.message.DefaultFlowMessageFactory;
 import org.apache.logging.log4j.message.LocalizedMessage;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.MessageFactory;
+import org.apache.logging.log4j.spi.AbstractLogger;
 import org.apache.logging.log4j.spi.ExtendedLogger;
 import org.apache.logging.log4j.status.StatusLogger;
 
@@ -46,14 +51,16 @@ import org.apache.logging.log4j.status.StatusLogger;
  */
 public class ApiLogger extends Logger {
 
-    private final WrappedLogger logger;
+    private static final org.apache.logging.log4j.Logger LOGGER = 
StatusLogger.getLogger();
+
+    private final ExtendedLogger logger;
     private static final String FQCN = ApiLogger.class.getName();
 
-    ApiLogger(final ExtendedLogger logger) {
+    protected ApiLogger(final ExtendedLogger logger) {
         super(logger.getName(), null);
         final Level javaLevel = LevelTranslator.toJavaLevel(logger.getLevel());
         super.setLevel(javaLevel);
-        this.logger = new WrappedLogger(logger);
+        this.logger = logger;
     }
 
     @Override
@@ -72,11 +79,32 @@ public class ApiLogger extends Logger {
     }
 
     // support for Logger.getFilter()/Logger.setFilter()
-    boolean isFiltered(final LogRecord logRecord) {
+    private boolean isFiltered(final LogRecord logRecord) {
         final Filter filter = getFilter();
         return filter != null && !filter.isLoggable(logRecord);
     }
 
+    private boolean isFiltered(Level level, Throwable thrown, String msg, 
Object... params) {
+        final Filter filter = getFilter();
+        if (filter == null) {
+            return false;
+        }
+        LogRecord lr =
+                new LogRecord(level, params != null && params.length > 0 ? 
MessageFormat.format(msg, params) : msg);
+        lr.setThrown(thrown);
+        return !filter.isLoggable(lr);
+    }
+
+    private boolean isFiltered(Level level, Throwable thrown, Supplier<String> 
msgSupplier) {
+        final Filter filter = getFilter();
+        if (filter == null) {
+            return false;
+        }
+        LogRecord lr = new LogRecord(level, msgSupplier.get());
+        lr.setThrown(thrown);
+        return !filter.isLoggable(lr);
+    }
+
     @Override
     public boolean isLoggable(final Level level) {
         return logger.isEnabled(toLevel(level));
@@ -89,20 +117,7 @@ public class ApiLogger extends Logger {
 
     @Override
     public void setLevel(final Level newLevel) throws SecurityException {
-        StatusLogger.getLogger()
-                .error(
-                        "Cannot set JUL log level through log4j-api: " + 
"ignoring call to Logger.setLevel({})",
-                        newLevel);
-    }
-
-    /**
-     * Provides access to {@link Logger#setLevel(java.util.logging.Level)}. 
This method should only be used by child
-     * classes.
-     *
-     * @see Logger#setLevel(java.util.logging.Level)
-     */
-    protected void doSetLevel(final Level newLevel) throws SecurityException {
-        super.setLevel(newLevel);
+        LOGGER.error("Cannot set JUL log level through Log4j API: ignoring 
call to Logger.setLevel({})", newLevel);
     }
 
     /**
@@ -123,12 +138,11 @@ public class ApiLogger extends Logger {
         return () -> logger.getMessageFactory().newMessage(msgSupplier.get());
     }
 
-    private org.apache.logging.log4j.util.Supplier<LocalizedMessage> 
toLocalizedMessageSupplier(
-            ResourceBundle bundle, String msg) {
+    private org.apache.logging.log4j.util.Supplier<Message> 
toMessageSupplier(ResourceBundle bundle, String msg) {
         return () -> new LocalizedMessage(bundle, msg);
     }
 
-    private org.apache.logging.log4j.util.Supplier<LocalizedMessage> 
toLocalizedMessageSupplier(
+    private org.apache.logging.log4j.util.Supplier<Message> toMessageSupplier(
             ResourceBundle bundle, String msg, Object[] params) {
         return () -> new LocalizedMessage(bundle, msg, params);
     }
@@ -139,11 +153,10 @@ public class ApiLogger extends Logger {
 
     @Override
     public void log(final Level level, final String msg) {
-        if (hasFilter()) {
-            super.log(level, msg);
-        } else {
-            logger.log(toLevel(level), msg);
+        if (isFiltered(level, null, msg)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, toLevel(level), null, msg);
     }
 
     /**
@@ -151,38 +164,34 @@ public class ApiLogger extends Logger {
      */
     @Override
     public void log(Level level, Supplier<String> msgSupplier) {
-        if (hasFilter()) {
-            super.log(level, msgSupplier);
-        } else {
-            logger.log(toLevel(level), toLog4jSupplier(msgSupplier));
+        if (isFiltered(level, null, msgSupplier)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, toLevel(level), null, 
toLog4jSupplier(msgSupplier), null);
     }
 
     @Override
     public void log(final Level level, final String msg, final Object param1) {
-        if (hasFilter()) {
-            super.log(level, msg, param1);
-        } else {
-            logger.log(toLevel(level), msg, param1);
+        if (isFiltered(level, null, msg, param1)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, toLevel(level), null, msg, param1);
     }
 
     @Override
     public void log(final Level level, final String msg, final Object[] 
params) {
-        if (hasFilter()) {
-            super.log(level, msg, params);
-        } else {
-            logger.log(toLevel(level), msg, params);
+        if (isFiltered(level, null, msg, params)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, toLevel(level), null, msg, params);
     }
 
     @Override
     public void log(final Level level, final String msg, final Throwable 
thrown) {
-        if (hasFilter()) {
-            super.log(level, msg, thrown);
-        } else {
-            logger.log(toLevel(level), msg, thrown);
+        if (isFiltered(level, thrown, msg)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, toLevel(level), null, msg, thrown);
     }
 
     /**
@@ -190,22 +199,20 @@ public class ApiLogger extends Logger {
      */
     @Override
     public void log(Level level, Throwable thrown, Supplier<String> 
msgSupplier) {
-        if (hasFilter()) {
-            super.log(level, thrown, msgSupplier);
-        } else {
-            logger.log(toLevel(level), toLog4jSupplier(msgSupplier), thrown);
+        if (isFiltered(level, thrown, msgSupplier)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, toLevel(level), null, 
toLog4jSupplier(msgSupplier), thrown);
     }
 
     @Override
     public void logp(final Level level, final String sourceClass, final String 
sourceMethod, final String msg) {
-        if (hasFilter()) {
-            super.logp(level, sourceClass, sourceMethod, msg);
-        } else {
-            logger.atLevel(toLevel(level))
-                    .withLocation(toLocation(sourceClass, sourceMethod))
-                    .log(msg);
+        if (isFiltered(level, null, msg)) {
+            return;
         }
+        logger.atLevel(toLevel(level))
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .log(msg);
     }
 
     /**
@@ -213,13 +220,12 @@ public class ApiLogger extends Logger {
      */
     @Override
     public void logp(Level level, String sourceClass, String sourceMethod, 
Supplier<String> msgSupplier) {
-        if (hasFilter()) {
-            super.logp(level, sourceClass, sourceMethod, msgSupplier);
-        } else {
-            logger.atLevel(toLevel(level))
-                    .withLocation(toLocation(sourceClass, sourceMethod))
-                    .log(toMessageSupplier(msgSupplier));
+        if (isFiltered(level, null, msgSupplier)) {
+            return;
         }
+        logger.atLevel(toLevel(level))
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .log(toMessageSupplier(msgSupplier));
     }
 
     @Override
@@ -229,13 +235,12 @@ public class ApiLogger extends Logger {
             final String sourceMethod,
             final String msg,
             final Object param1) {
-        if (hasFilter()) {
-            super.logp(level, sourceClass, sourceMethod, msg, param1);
-        } else {
-            logger.atLevel(toLevel(level))
-                    .withLocation(toLocation(sourceClass, sourceMethod))
-                    .log(msg, param1);
+        if (isFiltered(level, null, msg, param1)) {
+            return;
         }
+        logger.atLevel(toLevel(level))
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .log(msg, param1);
     }
 
     @Override
@@ -245,13 +250,12 @@ public class ApiLogger extends Logger {
             final String sourceMethod,
             final String msg,
             final Object[] params) {
-        if (hasFilter()) {
-            super.logp(level, sourceClass, sourceMethod, msg, params);
-        } else {
-            logger.atLevel(toLevel(level))
-                    .withLocation(toLocation(sourceClass, sourceMethod))
-                    .log(msg, params);
+        if (isFiltered(level, null, msg, params)) {
+            return;
         }
+        logger.atLevel(toLevel(level))
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .log(msg, params);
     }
 
     @Override
@@ -261,14 +265,13 @@ public class ApiLogger extends Logger {
             final String sourceMethod,
             final String msg,
             final Throwable thrown) {
-        if (hasFilter()) {
-            super.logp(level, sourceClass, sourceMethod, msg, thrown);
-        } else {
-            logger.atLevel(toLevel(level))
-                    .withLocation(toLocation(sourceClass, sourceMethod))
-                    .withThrowable(thrown)
-                    .log(msg);
+        if (isFiltered(level, thrown, msg)) {
+            return;
         }
+        logger.atLevel(toLevel(level))
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withThrowable(thrown)
+                .log(msg);
     }
 
     /**
@@ -277,14 +280,13 @@ public class ApiLogger extends Logger {
     @Override
     public void logp(
             Level level, String sourceClass, String sourceMethod, Throwable 
thrown, Supplier<String> msgSupplier) {
-        if (hasFilter()) {
-            super.logp(level, sourceClass, sourceMethod, thrown, msgSupplier);
-        } else {
-            logger.atLevel(toLevel(level))
-                    .withLocation(toLocation(sourceClass, sourceMethod))
-                    .withThrowable(thrown)
-                    .log(toMessageSupplier(msgSupplier));
+        if (isFiltered(level, thrown, msgSupplier)) {
+            return;
         }
+        logger.atLevel(toLevel(level))
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withThrowable(thrown)
+                .log(toMessageSupplier(msgSupplier));
     }
 
     /**
@@ -293,26 +295,24 @@ public class ApiLogger extends Logger {
     @Override
     public void logrb(
             Level level, String sourceClass, String sourceMethod, 
ResourceBundle bundle, String msg, Object... params) {
-        if (hasFilter()) {
-            super.logrb(level, sourceClass, sourceMethod, bundle, msg, params);
-        } else {
-            logger.atLevel(toLevel(level))
-                    .withLocation(toLocation(sourceClass, sourceMethod))
-                    .log(toLocalizedMessageSupplier(bundle, msg, params));
+        if (isFiltered(level, null, msg, params)) {
+            return;
         }
+        logger.atLevel(toLevel(level))
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .log(toMessageSupplier(bundle, msg, params));
     }
 
     @Override
     public void logrb(
             Level level, String sourceClass, String sourceMethod, 
ResourceBundle bundle, String msg, Throwable thrown) {
-        if (hasFilter()) {
-            super.logrb(level, sourceClass, sourceMethod, bundle, msg, thrown);
-        } else {
-            logger.atLevel(toLevel(level))
-                    .withLocation(toLocation(sourceClass, sourceMethod))
-                    .withThrowable(thrown)
-                    .log(toLocalizedMessageSupplier(bundle, msg));
+        if (isFiltered(level, thrown, msg)) {
+            return;
         }
+        logger.atLevel(toLevel(level))
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withThrowable(thrown)
+                .log(toMessageSupplier(bundle, msg));
     }
 
     /**
@@ -320,11 +320,10 @@ public class ApiLogger extends Logger {
      */
     @Override
     public void logrb(Level level, ResourceBundle bundle, String msg, 
Object... params) {
-        if (hasFilter()) {
-            super.logrb(level, bundle, msg, params);
-        } else {
-            logger.log(toLevel(level), toLocalizedMessageSupplier(bundle, msg, 
params));
+        if (isFiltered(level, null, msg, params)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, toLevel(level), null, 
toMessageSupplier(bundle, msg, params), null);
     }
 
     /**
@@ -332,50 +331,101 @@ public class ApiLogger extends Logger {
      */
     @Override
     public void logrb(Level level, ResourceBundle bundle, String msg, 
Throwable thrown) {
-        if (hasFilter()) {
-            super.logrb(level, bundle, msg, thrown);
-        } else {
-            
logger.atLevel(toLevel(level)).withThrowable(thrown).log(toLocalizedMessageSupplier(bundle,
 msg));
+        if (isFiltered(level, thrown, msg)) {
+            return;
+        }
+        LogBuilder builder = 
logger.atLevel(toLevel(level)).withThrowable(thrown);
+        if (builder instanceof BridgeAware bridgeAware) {
+            bridgeAware.setEntryPoint(FQCN);
         }
+        builder.log(toMessageSupplier(bundle, msg));
     }
 
     @Override
     public void entering(final String sourceClass, final String sourceMethod) {
-        logger.traceEntry();
+        if (isFiltered(Level.FINER, null, "ENTRY")) {
+            return;
+        }
+        logger.atTrace()
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withMarker(AbstractLogger.ENTRY_MARKER)
+                .log(DefaultFlowMessageFactory.INSTANCE.newEntryMessage(null, 
(Object[]) null));
     }
 
     @Override
     public void entering(final String sourceClass, final String sourceMethod, 
final Object param1) {
-        logger.traceEntry(null, param1);
+        if (isFiltered(Level.FINER, null, "ENTRY {0}", param1)) {
+            return;
+        }
+        logger.atTrace()
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withMarker(AbstractLogger.ENTRY_MARKER)
+                .log(DefaultFlowMessageFactory.INSTANCE.newEntryMessage(null, 
param1));
     }
 
     @Override
     public void entering(final String sourceClass, final String sourceMethod, 
final Object[] params) {
-        logger.traceEntry(null, params);
+        if (hasFilter()) {
+            // Emulate standard behavior
+            if (!isLoggable(Level.FINER)) {
+                return;
+            }
+            final StringBuilder b = new StringBuilder("ENTRY");
+            if (params != null) {
+                for (int i = 0; i < params.length; i++) {
+                    b.append(' ').append('{').append(i).append('}');
+                }
+            }
+            if (isFiltered(Level.FINER, null, b.toString(), params)) {
+                return;
+            }
+        }
+        logger.atTrace()
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withMarker(AbstractLogger.ENTRY_MARKER)
+                .log(DefaultFlowMessageFactory.INSTANCE.newEntryMessage(null, 
params));
     }
 
     @Override
     public void exiting(final String sourceClass, final String sourceMethod) {
-        logger.traceExit();
+        if (isFiltered(Level.FINER, null, "RETURN")) {
+            return;
+        }
+        logger.atTrace()
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withMarker(AbstractLogger.EXIT_MARKER)
+                .log(DefaultFlowMessageFactory.INSTANCE.newExitMessage(null, 
(Object) null));
     }
 
     @Override
     public void exiting(final String sourceClass, final String sourceMethod, 
final Object result) {
-        logger.traceExit(result);
+        if (isFiltered(Level.FINER, null, "RETURN {0}", result)) {
+            return;
+        }
+        logger.atTrace()
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withMarker(AbstractLogger.EXIT_MARKER)
+                .log(DefaultFlowMessageFactory.INSTANCE.newExitMessage(null, 
result));
     }
 
     @Override
     public void throwing(final String sourceClass, final String sourceMethod, 
final Throwable thrown) {
-        logger.throwing(thrown);
+        if (isFiltered(Level.FINER, thrown, "THROW")) {
+            return;
+        }
+        logger.atTrace()
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withMarker(AbstractLogger.THROWING_MARKER)
+                .withThrowable(thrown)
+                .log("Throwing");
     }
 
     @Override
     public void severe(final String msg) {
-        if (hasFilter()) {
-            super.severe(msg);
-        } else {
-            logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.ERROR, 
null, msg);
+        if (isFiltered(Level.SEVERE, null, msg)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.ERROR, null, 
msg);
     }
 
     /**
@@ -383,119 +433,106 @@ public class ApiLogger extends Logger {
      */
     @Override
     public void severe(Supplier<String> msgSupplier) {
-        if (hasFilter()) {
-            super.severe(msgSupplier);
-        } else {
-            logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.ERROR, 
null, toMessageSupplier(msgSupplier), null);
+        if (isFiltered(Level.SEVERE, null, msgSupplier)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.ERROR, null, 
toLog4jSupplier(msgSupplier), null);
     }
 
     @Override
     public void warning(final String msg) {
-        if (hasFilter()) {
-            super.warning(msg);
-        } else {
-            logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.WARN, 
null, msg);
+        if (isFiltered(Level.WARNING, null, msg)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.WARN, null, 
msg);
     }
 
     @Override
     public void warning(Supplier<String> msgSupplier) {
-        if (hasFilter()) {
-            super.warning(msgSupplier);
-        } else {
-            logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.WARN, 
null, toMessageSupplier(msgSupplier), null);
+        if (isFiltered(Level.WARNING, null, msgSupplier)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.WARN, null, 
toLog4jSupplier(msgSupplier), null);
     }
 
     @Override
     public void info(final String msg) {
-        if (hasFilter()) {
-            super.info(msg);
-        } else {
-            logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.INFO, 
null, msg);
+        if (isFiltered(Level.INFO, null, msg)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.INFO, null, 
msg);
     }
 
     @Override
     public void info(Supplier<String> msgSupplier) {
-        if (hasFilter()) {
-            super.info(msgSupplier);
-        } else {
-            logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.INFO, 
null, toMessageSupplier(msgSupplier), null);
+        if (isFiltered(Level.INFO, null, msgSupplier)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.INFO, null, 
toLog4jSupplier(msgSupplier), null);
     }
 
     @Override
     public void config(final String msg) {
-        if (hasFilter()) {
-            super.config(msg);
-        } else {
-            logger.logIfEnabled(FQCN, LevelTranslator.CONFIG, null, msg);
+        if (isFiltered(Level.CONFIG, null, msg)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, LevelTranslator.CONFIG, null, msg);
     }
 
     @Override
     public void config(Supplier<String> msgSupplier) {
-        if (hasFilter()) {
-            super.config(msgSupplier);
-        } else {
-            logger.logIfEnabled(FQCN, LevelTranslator.CONFIG, null, 
toMessageSupplier(msgSupplier), null);
+        if (isFiltered(Level.CONFIG, null, msgSupplier)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, LevelTranslator.CONFIG, null, 
toLog4jSupplier(msgSupplier), null);
     }
 
     @Override
     public void fine(final String msg) {
-        if (hasFilter()) {
-            super.fine(msg);
-        } else {
-            logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.DEBUG, 
null, msg);
+        if (isFiltered(Level.FINE, null, msg)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.DEBUG, null, 
msg);
     }
 
     @Override
     public void fine(Supplier<String> msgSupplier) {
-        if (hasFilter()) {
-            super.fine(msgSupplier);
-        } else {
-            logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.DEBUG, 
null, toMessageSupplier(msgSupplier), null);
+        if (isFiltered(Level.FINE, null, msgSupplier)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.DEBUG, null, 
toLog4jSupplier(msgSupplier), null);
     }
 
     @Override
     public void finer(final String msg) {
-        if (hasFilter()) {
-            super.finer(msg);
-        } else {
-            logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.TRACE, 
null, msg);
+        if (isFiltered(Level.FINER, null, msg)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.TRACE, null, 
msg);
     }
 
     @Override
     public void finer(Supplier<String> msgSupplier) {
-        if (hasFilter()) {
-            super.finer(msgSupplier);
-        } else {
-            logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.TRACE, 
null, toMessageSupplier(msgSupplier), null);
+        if (isFiltered(Level.FINER, null, msgSupplier)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.TRACE, null, 
toLog4jSupplier(msgSupplier), null);
     }
 
     @Override
     public void finest(final String msg) {
-        if (hasFilter()) {
-            super.finest(msg);
-        } else {
-            logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, msg);
+        if (isFiltered(Level.FINEST, null, msg)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, msg);
     }
 
     @Override
     public void finest(Supplier<String> msgSupplier) {
-        if (hasFilter()) {
-            super.finest(msgSupplier);
-        } else {
-            logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, 
toMessageSupplier(msgSupplier), null);
+        if (isFiltered(Level.FINEST, null, msgSupplier)) {
+            return;
         }
+        logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, 
toLog4jSupplier(msgSupplier), null);
     }
 
     private boolean hasFilter() {
diff --git 
a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/CoreLogger.java 
b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/CoreLogger.java
deleted file mode 100644
index ebc06985a8..0000000000
--- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/CoreLogger.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.jul;
-
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Log4j Core implementation of the JUL {@link Logger} class. <strong>Note 
that this implementation does
- * <em>not</em> use the {@link java.util.logging.Handler} class.</strong> 
Instead, logging is delegated to the
- * underlying Log4j {@link org.apache.logging.log4j.core.Logger} which uses
- * {@link org.apache.logging.log4j.core.Appender Appenders} instead.
- *
- * @since 2.1
- */
-public class CoreLogger extends ApiLogger {
-
-    private final org.apache.logging.log4j.core.Logger logger;
-
-    /**
-     * Constructs a Logger using a Log4j {@link 
org.apache.logging.log4j.core.Logger}.
-     *
-     * @param logger the underlying Logger to base this Logger on
-     */
-    CoreLogger(final org.apache.logging.log4j.core.Logger logger) {
-        super(logger);
-        this.logger = logger;
-    }
-
-    @Override
-    public void setLevel(final Level level) throws SecurityException {
-        super.doSetLevel(level); // checks permissions
-        logger.setLevel(LevelTranslator.toLevel(level));
-    }
-
-    /**
-     * Marks the underlying {@link org.apache.logging.log4j.core.Logger} as 
additive.
-     *
-     * @param additive {@code true} if this Logger should be additive
-     * @see org.apache.logging.log4j.core.Logger#setAdditive(boolean)
-     */
-    @Override
-    public synchronized void setUseParentHandlers(final boolean additive) {
-        logger.setAdditive(additive);
-    }
-
-    /**
-     * Indicates if the underlying {@link 
org.apache.logging.log4j.core.Logger} is additive. <strong>Note that the
-     * Log4j version of JDK Loggers do <em>not</em> use Handlers.</strong>
-     *
-     * @return {@code true} if this Logger is additive, or {@code false} 
otherwise
-     * @see org.apache.logging.log4j.core.Logger#isAdditive()
-     */
-    @Override
-    public synchronized boolean getUseParentHandlers() {
-        return logger.isAdditive();
-    }
-
-    @Override
-    public Logger getParent() {
-        final org.apache.logging.log4j.core.Logger parent = logger.getParent();
-        return parent == null ? null : Logger.getLogger(parent.getName());
-    }
-}
diff --git 
a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/CoreLoggerAdapter.java 
b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/CoreLoggerAdapter.java
deleted file mode 100644
index 4ca968f197..0000000000
--- 
a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/CoreLoggerAdapter.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.jul;
-
-import java.util.logging.Logger;
-import org.apache.logging.log4j.message.MessageFactory;
-import org.apache.logging.log4j.message.MessageFormatMessageFactory;
-import org.apache.logging.log4j.spi.LoggerContext;
-
-/**
- * {@link Logger} registry implementation that uses log4j-core.
- *
- * @since 2.1
- */
-public class CoreLoggerAdapter extends AbstractLoggerAdapter {
-
-    private static final MessageFactory MESSAGE_FACTORY = new 
MessageFormatMessageFactory();
-
-    @Override
-    protected Logger newLogger(final String name, final LoggerContext context) 
{
-        final org.apache.logging.log4j.spi.ExtendedLogger original = 
context.getLogger(name, MESSAGE_FACTORY);
-        if (original instanceof org.apache.logging.log4j.core.Logger) {
-            return new CoreLogger((org.apache.logging.log4j.core.Logger) 
original);
-        }
-        return new ApiLogger(original); // LOG4J2-1618 during shutdown, a 
SimpleLogger may be returned
-    }
-}
diff --git 
a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/Log4jBridgeHandler.java 
b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/Log4jBridgeHandler.java
index d5c44ecdc7..2f7a0f82a5 100644
--- 
a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/Log4jBridgeHandler.java
+++ 
b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/Log4jBridgeHandler.java
@@ -17,89 +17,77 @@
 package org.apache.logging.log4j.jul;
 
 // note: NO import of Logger, Level, LogManager to prevent conflicts JUL/log4j
+
+import aQute.bnd.annotation.Cardinality;
+import aQute.bnd.annotation.spi.ServiceConsumer;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Consumer;
+import java.util.ServiceLoader;
 import java.util.logging.LogRecord;
-import org.apache.logging.log4j.core.LoggerContext;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.LoggerConfig;
 import org.apache.logging.log4j.spi.ExtendedLogger;
 import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.ServiceLoaderUtil;
 
 /**
- * Bridge from JUL to log4j2.<br>
- * This is an alternative to log4j.jul.LogManager (running as complete JUL 
replacement),
- * especially useful for webapps running on a container for which the 
LogManager cannot or
- * should not be used.<br><br>
- *
- * Installation/usage:<ul>
- * <li> Declaratively inside JUL's <code>logging.properties</code>:<br>
- *    <code>handlers = 
org.apache.logging.log4j.jul.Log4jBridgeHandler</code><br>
- *    (and typically also:   
<code>org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels = 
true</code> )<br>
- *    Note: in a webapp running on Tomcat, you may create a 
<code>WEB-INF/classes/logging.properties</code>
- *    file to configure JUL for this webapp only: configured handlers and log 
levels affect your webapp only!
- *    This file is then the <i>complete</i> JUL configuration, so JUL's 
defaults (e.g. log level INFO) apply
- *    for stuff not explicitly defined therein.
- * <li> Programmatically by calling <code>install()</code> method,
- *    e.g. inside ServletContextListener static-class-init. or 
contextInitialized()
- * </ul>
- * Configuration (in JUL's <code>logging.properties</code>):<ul>
- * <li> Log4jBridgeHandler.<code>suffixToAppend</code><br>
- *        String, suffix to append to JUL logger names, to easily recognize 
bridged log messages.
- *        A dot "." is automatically prepended, so configuration for the basis 
logger is used<br>
- *        Example:  <code>Log4jBridgeHandler.suffixToAppend = _JUL</code><br>
- *        Useful, for example, if you use JSF because it logs exceptions and 
throws them afterwards;
- *        you can easily recognize the duplicates with this (or concentrate on 
the non-JUL-logs).
- * <li> Log4jBridgeHandler.<code>propagateLevels</code>   boolean, "true" to 
automatically propagate log4j log levels to JUL.
- * <li> Log4jBridgeHandler.<code>sysoutDebug</code>   boolean, perform some 
(developer) debug output to sysout
- * </ul>
- *
- * Log levels are translated with {@link LevelTranslator}, see also
- * <a 
href="https://logging.apache.org/log4j/2.x/log4j-jul/index.html#Default_Level_Conversions";>log4j
 doc</a>.<br><br>
+ * Bridge from JUL to Log4j API
+ * <p>
+ *     This is an alternative to {@link LogManager} (running as complete JUL 
replacement), especially useful for
+ *     webapps running on a container for which the LogManager cannot or 
should not be used.
+ * </p>
  *
- * Restrictions:<ul>
- * <li> Manually given source/location info in JUL (e.g. entering(), 
exiting(), throwing(), logp(), logrb() )
- *    will NOT be considered, i.e. gets lost in log4j logging.
- * <li> Log levels of JUL have to be adjusted according to log4j log levels:
- *      Either by using "propagateLevels" (preferred), or manually by 
specifying them explicitly,
- *      i.e. logging.properties and log4j2.xml have some redundancies.
- * <li> Only JUL log events that are allowed according to the JUL log level 
get to this handler and thus to log4j.
- *      This is only relevant and important if you NOT use "propagateLevels".
- *      If you set <code>.level = SEVERE</code> only error logs will be seen 
by this handler and thus log4j
- *      - even if the corresponding log4j log level is ALL.<br>
- *      On the other side, you should NOT set <code>.level = FINER  or  
FINEST</code> if the log4j level is higher.
- *      In this case a lot of JUL log events would be generated, sent via this 
bridge to log4j and thrown away by the latter.<br>
- *      Note: JUL's default log level (i.e. none specified in 
logger.properties) is INFO.
+ * <p>
+ *     Installation/usage:
+ * </p>
+ * <ul>
+ *     <li>
+ *         <p>Declaratively inside JUL's {@code logging.properties}):</p>
+ *         <pre>
+ *     handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler
+ *     # Enable log level propagation
+ *     # This requires `log4j-jul-propagator` to be present
+ *     org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels = true
+ *         </pre>
+ *         <p><strong>Note:</strong> if your application is running on Tomcat 
and has a
+ *         {@code WEB-INF/classes/logging.properties} class, the configured 
handlers and log level changes will
+ *         only affect you web application.</p>
+ *     </li>
+ *     <li>
+ *         Programmatically by calling {@link #install(boolean, String, 
boolean)}, e.g., inside a
+ *         <a 
href="https://jakarta.ee/specifications/platform/11/apidocs/jakarta/servlet/servletcontextlistener";>ServletContextListener</a>.
+ *     </li>
  * </ul>
  *
- * (Credits: idea and concept originate from 
org.slf4j.bridge.SLF4JBridgeHandler;
- *   level propagation idea originates from logback/LevelChangePropagator;
- *   but no source code has been copied)
- *
+ * <p>
+ *     <strong>Credits</strong>: the idea and concept originate from {@code 
org.slf4j.bridge.SLF4JBridgeHandler}.
+ *     The level propagation idea originates from {@code 
ch.qos.logback.classic.jul.LevelChangePropagator}.
+ *     No source code has been copied.
+ * </p>
+ * @see <a href="https://logging.apache.org/log4j/3.x/log4j-jul.html";>Log4j 
documentation site</a>
  * @since 2.15.0
  */
-public class Log4jBridgeHandler extends java.util.logging.Handler implements 
Consumer<Configuration> {
-    private static final org.apache.logging.log4j.Logger SLOGGER = 
StatusLogger.getLogger();
+@ServiceConsumer(value = Log4jBridgeHandler.LevelPropagator.class, cardinality 
= Cardinality.SINGLE)
+public class Log4jBridgeHandler extends java.util.logging.Handler {
+    private static final org.apache.logging.log4j.Logger LOGGER = 
StatusLogger.getLogger();
 
     // the caller of the logging is java.util.logging.Logger (for location 
info)
     private static final String FQCN = 
java.util.logging.Logger.class.getName();
     private static final String UNKNOWN_LOGGER_NAME = "unknown.jul.logger";
     private static final java.util.logging.Formatter julFormatter = new 
java.util.logging.SimpleFormatter();
+    private static final String DOC_URL =
+            
"https://logging.apache.org/log4j/3.x/log4j-jul.html##bridge-handler-propagator";;
 
     private boolean doDebugOutput = false;
     private String julSuffixToAppend = null;
+    private LevelPropagator levelPropagator;
     private volatile boolean installAsLevelPropagator = false;
 
     /**
      * Adds a new Log4jBridgeHandler instance to JUL's root logger.
-     * This is a programmatic alternative to specify
-     * <code>handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler</code>
-     * and its configuration in logging.properties.<br>
-     * @param removeHandlersForRootLogger  true to remove all other installed 
handlers on JUL root level
+     * <p>
+     *     This is a programmatic alternative for a {@code logging.properties} 
file.
+     * </p>
+     * @param removeHandlersForRootLogger  If {@code true}, remove all other 
installed handlers on JUL root level
+     * @param suffixToAppend The suffix to append to each JUL logger
+     * @param propagateLevels If {@code true}, the logging implementation 
levels will propagate to JUL.
      */
     public static void install(boolean removeHandlersForRootLogger, String 
suffixToAppend, boolean propagateLevels) {
         final java.util.logging.Logger rootLogger = getJulRootLogger();
@@ -108,8 +96,7 @@ public class Log4jBridgeHandler extends 
java.util.logging.Handler implements Con
                 rootLogger.removeHandler(hdl);
             }
         }
-        rootLogger.addHandler(new Log4jBridgeHandler(false, suffixToAppend, 
propagateLevels));
-        // note: filter-level of Handler defaults to ALL, so nothing to do here
+        rootLogger.addHandler(new Log4jBridgeHandler(suffixToAppend, 
propagateLevels));
     }
 
     private static java.util.logging.Logger getJulRootLogger() {
@@ -120,28 +107,21 @@ public class Log4jBridgeHandler extends 
java.util.logging.Handler implements Con
     public Log4jBridgeHandler() {
         final java.util.logging.LogManager julLogMgr = 
java.util.logging.LogManager.getLogManager();
         final String className = this.getClass().getName();
-        init(
-                Boolean.parseBoolean(julLogMgr.getProperty(className + 
".sysoutDebug")),
+        configure(
                 julLogMgr.getProperty(className + ".appendSuffix"),
                 Boolean.parseBoolean(julLogMgr.getProperty(className + 
".propagateLevels")));
     }
 
     /** Initialize this handler with given configuration. */
-    public Log4jBridgeHandler(boolean debugOutput, String suffixToAppend, 
boolean propagateLevels) {
-        init(debugOutput, suffixToAppend, propagateLevels);
+    private Log4jBridgeHandler(String suffixToAppend, boolean propagateLevels) 
{
+        configure(suffixToAppend, propagateLevels);
     }
 
     /** Perform init. of this handler with given configuration (typical use is 
for constructor). */
     @SuppressFBWarnings(
             value = "INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE",
             justification = "The data is available only in debug mode.")
-    protected void init(boolean debugOutput, String suffixToAppend, boolean 
propagateLevels) {
-        this.doDebugOutput = debugOutput;
-        if (debugOutput) {
-            new Exception("DIAGNOSTIC ONLY (sysout):  Log4jBridgeHandler 
instance created (" + this + ")")
-                    .printStackTrace(System.out); // is no error thus no syserr
-        }
-
+    private void configure(String suffixToAppend, boolean propagateLevels) {
         if (suffixToAppend != null) {
             suffixToAppend = suffixToAppend.trim(); // remove spaces
             if (suffixToAppend.isEmpty()) {
@@ -152,9 +132,23 @@ public class Log4jBridgeHandler extends 
java.util.logging.Handler implements Con
         }
         this.julSuffixToAppend = suffixToAppend;
 
-        this.installAsLevelPropagator = propagateLevels;
+        installAsLevelPropagator = propagateLevels;
+        if (installAsLevelPropagator) {
+            levelPropagator = ServiceLoaderUtil.safeStream(
+                            LevelPropagator.class,
+                            ServiceLoader.load(LevelPropagator.class, 
getClass().getClassLoader()),
+                            LOGGER)
+                    .findAny()
+                    .orElse(null);
+            if (levelPropagator == null) {
+                LOGGER.warn(
+                        "A JUL level propagator was requested, but no 
implementation was found.\nSee {} for details.",
+                        DOC_URL);
+                installAsLevelPropagator = false;
+            }
+        }
 
-        SLOGGER.debug(
+        LOGGER.debug(
                 "Log4jBridgeHandler init. with: suffix='{}', lvlProp={}, 
instance={}",
                 suffixToAppend,
                 propagateLevels,
@@ -163,11 +157,8 @@ public class Log4jBridgeHandler extends 
java.util.logging.Handler implements Con
 
     @Override
     public void close() {
-        // cleanup and remove listener and JUL logger references
-        julLoggerRefs = null;
-        
LoggerContext.getContext(false).removeConfigurationStartedListener(this);
-        if (doDebugOutput) {
-            System.out.println("sysout:  Log4jBridgeHandler close(): " + this);
+        if (levelPropagator != null) {
+            levelPropagator.stop();
         }
     }
 
@@ -178,29 +169,18 @@ public class Log4jBridgeHandler extends 
java.util.logging.Handler implements Con
         }
 
         // Only execute synchronized code if we really have to
-        if (this.installAsLevelPropagator) {
+        if (installAsLevelPropagator) {
             synchronized (this) {
                 // Check again to make sure we still have to propagate  the 
levels at this point
-                if (this.installAsLevelPropagator) {
-                    // no need to close the AutoCloseable ctx here
-                    @SuppressWarnings("resource")
-                    final LoggerContext context = 
LoggerContext.getContext(false);
-                    context.addConfigurationStartedListener(this);
-                    propagateLogLevels(context.getConfiguration());
-                    // note: 
java.util.logging.LogManager.addPropertyChangeListener() could also
-                    // be set here, but a call of JUL.readConfiguration() will 
be done on purpose
-                    this.installAsLevelPropagator = false;
+                if (installAsLevelPropagator) {
+                    levelPropagator.start();
+                    installAsLevelPropagator = false;
                 }
             }
         }
 
         final org.apache.logging.log4j.Logger log4jLogger = 
getLog4jLogger(record);
-        final String msg = julFormatter.formatMessage(record); // use JUL's 
implementation to get real msg
-        /* log4j allows nulls:
-        if (msg == null) {
-            // JUL allows nulls, but other log system may not
-            msg = "<null log msg>";
-        } */
+        final String msg = julFormatter.formatMessage(record);
         final org.apache.logging.log4j.Level log4jLevel = 
LevelTranslator.toLevel(record.getLevel());
         final Throwable thrown = record.getThrown();
         if (log4jLogger instanceof ExtendedLogger) {
@@ -237,89 +217,29 @@ public class Log4jBridgeHandler extends 
java.util.logging.Handler implements Con
         return org.apache.logging.log4j.LogManager.getLogger(name);
     }
 
-    /////  log level propagation code
-
-    @Override
-    public void accept(final Configuration configuration) {
-        SLOGGER.debug("Log4jBridgeHandler.accept(): {}", configuration);
-        propagateLogLevels(configuration);
-    }
-
-    /** Save "hard" references to configured JUL loggers. (is lazy init.) */
-    private Set<java.util.logging.Logger> julLoggerRefs;
-    /** Perform developer tests? (Should be unused/outcommented for real code) 
*/
-    // private static final boolean DEVTEST = false;
-
-    private void propagateLogLevels(final Configuration config) {
-        SLOGGER.debug("Log4jBridgeHandler.propagateLogLevels(): {}", config);
-        // clear or init. saved JUL logger references
-        // JUL loggers have to be explicitly referenced because JUL internally 
uses
-        // weak references so not instantiated loggers may be garbage collected
-        // and their level config gets lost then.
-        if (julLoggerRefs == null) {
-            julLoggerRefs = new HashSet<>();
-        } else {
-            julLoggerRefs.clear();
-        }
+    /**
+     * Propagates level configuration from the Log4j API implementation used
+     * <p>
+     *     Using the {@link Log4jBridgeHandler} is expensive, since disabled 
log events must be formatted by JUL,
+     *     before they can be dropped by the Log4j API implementation.
+     * </p>
+     * <p>
+     *     This class introduces a mechanism that can be implemented by each 
Log4j API implementation to be notified,
+     *     whenever a {@link Log4jBridgeHandler} is used. The logging 
implementation will be able to synchronize
+     *     its levels with the levels of JUL loggers each time it is 
reconfigured.
+     * </p>
+     * @since 3.0.0
+     */
+    public interface LevelPropagator {
 
-        // if (DEVTEST)  debugPrintJulLoggers("Start of propagation");
-        // walk through all log4j configured loggers and set JUL level 
accordingly
-        final Map<String, LoggerConfig> log4jLoggers = config.getLoggers();
-        // java.util.List<String> outTxt = new java.util.ArrayList<>();    // 
DEVTEST / DEV-DEBUG ONLY
-        for (LoggerConfig lcfg : log4jLoggers.values()) {
-            final java.util.logging.Logger julLog =
-                    java.util.logging.Logger.getLogger(lcfg.getName()); // 
this also fits for root = ""
-            final java.util.logging.Level julLevel =
-                    LevelTranslator.toJavaLevel(lcfg.getLevel()); // 
lcfg.getLevel() never returns null
-            julLog.setLevel(julLevel);
-            julLoggerRefs.add(julLog); // save an explicit reference to 
prevent GC
-            // if (DEVTEST)  outTxt.add("propagating '" + lcfg.getName() + "' 
/ " + lcfg.getLevel() + "  ->  " +
-            // julLevel);
-        } // for
-        // if (DEVTEST)  java.util.Collections.sort(outTxt, 
String.CASE_INSENSITIVE_ORDER);
-        // if (DEVTEST)  for (String s : outTxt)  System.out.println("+ " + s);
-        // if (DEVTEST)  debugPrintJulLoggers("After propagation");
+        /**
+         * Start propagating log levels.
+         */
+        void start();
 
-        // cleanup JUL: reset all log levels not explicitly given by log4j
-        // This has to happen after propagation because JUL creates and inits. 
the loggers lazily
-        // so a nested logger might be created during the propagation-for-loop 
above and gets
-        // its JUL-configured level not until then.
-        final java.util.logging.LogManager julMgr = 
java.util.logging.LogManager.getLogManager();
-        for (Enumeration<String> en = julMgr.getLoggerNames(); 
en.hasMoreElements(); ) {
-            final java.util.logging.Logger julLog = 
julMgr.getLogger(en.nextElement());
-            if (julLog != null
-                    && julLog.getLevel() != null
-                    && !"".equals(julLog.getName())
-                    && !log4jLoggers.containsKey(julLog.getName())) {
-                julLog.setLevel(null);
-            }
-        } // for
-        // if (DEVTEST)  debugPrintJulLoggers("After JUL cleanup");
+        /**
+         * Stop propagating log levels.
+         */
+        void stop();
     }
-
-    /* DEV-DEBUG ONLY  (comment out for release) *xx/
-    private void debugPrintJulLoggers(String infoStr) {
-        if (!DEVTEST)  return;
-        java.util.logging.LogManager julMgr = 
java.util.logging.LogManager.getLogManager();
-        System.out.println("sysout:  " + infoStr + " - for " + julMgr);
-        java.util.List<String> txt = new java.util.ArrayList<>();
-        int n = 1;
-        for (Enumeration<String> en = julMgr.getLoggerNames();  
en.hasMoreElements(); ) {
-            String ln = en.nextElement();
-            java.util.logging.Logger lg = julMgr.getLogger(ln);
-            if (lg == null) {
-                txt.add("(!null-Logger '" + ln + "')  #" + n);
-            } else if (lg.getLevel() == null) {
-                txt.add("(null-Level Logger '" + ln + "')  #" + n);
-            } else {
-                txt.add("Logger '" + ln + "',  lvl = " + lg.getLevel() + "  #" 
+ n);
-            }
-            n++;
-        } // for
-        java.util.Collections.sort(txt, String.CASE_INSENSITIVE_ORDER);
-        for (String s : txt) {
-            System.out.println("  - " + s);
-        }
-    } /**/
-
 }
diff --git 
a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/WrappedLogger.java 
b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/WrappedLogger.java
deleted file mode 100644
index 4ef6009985..0000000000
--- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/WrappedLogger.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.jul;
-
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.Marker;
-import org.apache.logging.log4j.message.EntryMessage;
-import org.apache.logging.log4j.message.Message;
-import org.apache.logging.log4j.spi.ExtendedLogger;
-import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;
-
-/**
- * Wrapper class to ensure proper FQCN support in Logger calls.
- *
- * @since 2.1
- */
-class WrappedLogger extends ExtendedLoggerWrapper {
-
-    private static final long serialVersionUID = 1L;
-    private static final String FQCN = ApiLogger.class.getName();
-
-    WrappedLogger(final ExtendedLogger logger) {
-        super(logger, logger.getName(), logger.getMessageFactory());
-    }
-
-    @Override
-    public void log(final Level level, final String message, final Throwable 
t) {
-        logIfEnabled(FQCN, level, null, message, t);
-    }
-
-    @Override
-    public void log(final Level level, final String message, final Object... 
params) {
-        logIfEnabled(FQCN, level, null, message, params);
-    }
-
-    @Override
-    public void log(final Level level, final String message) {
-        logIfEnabled(FQCN, level, null, message);
-    }
-
-    @Override
-    public EntryMessage traceEntry() {
-        return enter(FQCN, null, (Object[]) null);
-    }
-
-    @Override
-    public EntryMessage traceEntry(final String message, final Object... 
params) {
-        return enter(FQCN, message, params);
-    }
-
-    @Override
-    public <T extends Throwable> T throwing(final T t) {
-        return throwing(FQCN, 
LevelTranslator.toLevel(java.util.logging.Level.FINER), t);
-    }
-
-    @Override
-    protected void log(
-            Level level, Marker marker, String fqcn, StackTraceElement 
location, Message message, Throwable throwable) {
-        logger.logMessage(level, marker, fqcn, location, message, throwable);
-    }
-}
diff --git 
a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/package-info.java 
b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/package-info.java
index dd22747306..dd534ee454 100644
--- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/package-info.java
+++ b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/package-info.java
@@ -15,7 +15,7 @@
  * limitations under the license.
  */
 @Export
-@Version("2.20.2")
+@Version("3.0.0")
 package org.apache.logging.log4j.jul;
 
 import org.osgi.annotation.bundle.Export;
diff --git 
a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/AbstractLoggerTest.java
 
b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/AbstractLoggerTest.java
index 303ff62db3..4bc75c3d5a 100644
--- 
a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/AbstractLoggerTest.java
+++ 
b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/AbstractLoggerTest.java
@@ -19,6 +19,7 @@ package org.apache.logging.log4j.jul.test;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.logging.Logger;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.core.LogEvent;
@@ -96,19 +97,21 @@ public abstract class AbstractLoggerTest {
     }
 
     @Test
-    public void testAlteringLogFilter() throws Exception {
+    public void testCountingLogFilter() throws Exception {
+        AtomicInteger counter = new AtomicInteger();
         logger.setFilter(record -> {
-            record.setMessage("This is not the message you are looking for.");
+            counter.incrementAndGet();
             return true;
         });
         logger.info("Informative message here.");
+        assertThat(counter).hasValue(1);
         final List<LogEvent> events = eventAppender.getEvents();
         assertThat(events).hasSize(1);
         final LogEvent event = events.get(0);
         assertThat(event).isInstanceOf(MementoLogEvent.class);
         assertThat(event.getLevel()).isEqualTo(Level.INFO);
         assertThat(event.getLoggerName()).isEqualTo(LOGGER_NAME);
-        assertThat(event.getMessage().getFormattedMessage()).isEqualTo("This 
is not the message you are looking for.");
+        
assertThat(event.getMessage().getFormattedMessage()).isEqualTo("Informative 
message here.");
         assertThat(event.getLoggerFqcn()).isEqualTo(ApiLogger.class.getName());
     }
 
diff --git 
a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CallerInformationTest.java
 
b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CallerInformationTest.java
index ce8c981f06..be15806615 100644
--- 
a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CallerInformationTest.java
+++ 
b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CallerInformationTest.java
@@ -18,17 +18,26 @@ package org.apache.logging.log4j.jul.test;
 
 import static org.junit.Assert.assertEquals;
 
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Filter;
 import java.util.logging.Level;
+import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 import org.apache.logging.log4j.core.test.appender.ListAppender;
 import org.apache.logging.log4j.core.test.junit.LoggerContextRule;
 import org.apache.logging.log4j.jul.LogManager;
 import org.junit.AfterClass;
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class CallerInformationTest {
 
     private static final String PARAM_1 = "PARAM_1";
@@ -49,17 +58,58 @@ public class CallerInformationTest {
         System.clearProperty("java.util.logging.manager");
     }
 
+    @Parameterized.Parameters
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] {{false}, {true}});
+    }
+
+    private final boolean withFilter;
+    private CountingFilter filter;
+
+    public CallerInformationTest(final boolean withFilter) {
+        this.withFilter = withFilter;
+    }
+
+    @Before
+    public void setup() {
+        filter = withFilter ? new CountingFilter() : null;
+        Logger.getLogger("ClassLogger").setFilter(filter);
+        Logger.getLogger("MethodLogger").setFilter(filter);
+    }
+
     @Test
     public void testClassLogger() throws Exception {
         final ListAppender app = ctx.getListAppender("Class").clear();
         final Logger logger = Logger.getLogger("ClassLogger");
-        logger.info("Ignored message contents.");
-        logger.warning("Verifying the caller class is still correct.");
-        logger.severe("Hopefully nobody breaks me!");
+        // Eager methods
+        logger.severe("CATASTROPHE INCOMING!");
+        logger.warning("ZOMBIES!!!");
+        logger.info("brains~~~");
+        logger.config("Config!");
+        logger.fine("Itchy. Tasty.");
+        logger.finer("Finer message.");
+        logger.finest("Finest message.");
+        logger.log(Level.FINEST, "Finest message.");
+        logger.log(Level.FINEST, "Message of level {1}.", Level.FINEST);
+        logger.log(Level.FINEST, "Hello {1} and {2}!.", new Object[] {"foo", 
"bar"});
+        // Lazy methods
+        logger.severe(() -> "CATASTROPHE INCOMING!");
+        logger.warning(() -> "ZOMBIES!!!");
+        logger.info(() -> "brains~~~");
+        logger.config(() -> "Config!");
+        logger.fine(() -> "Itchy. Tasty.");
+        logger.finer(() -> "Finer message.");
+        logger.finest(() -> "Finest message.");
+        logger.log(Level.FINEST, () -> "Finest message.");
+        logger.log(Level.FINEST, new RuntimeException(), () -> "Message with 
exception.");
         List<String> messages = app.getMessages();
-        assertEquals("Incorrect number of messages.", 3, messages.size());
-        for (final String message : messages) {
-            assertEquals("Incorrect caller class name.", 
this.getClass().getName(), message);
+        assertMessageCount(19, messages);
+        for (int i = 0; i < messages.size(); i++) {
+            String message = messages.get(i);
+            assertEquals(
+                    "Incorrect caller class name for message " + i,
+                    this.getClass().getName(),
+                    message);
         }
 
         // Test passing the location information directly
@@ -70,10 +120,17 @@ public class CallerInformationTest {
         logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello!", new 
RuntimeException());
         logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, () -> "Hello" + 
PARAM_1 + "!");
         logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, new 
RuntimeException(), () -> "Hello " + PARAM_1 + "!");
+        logger.entering(SOURCE_CLASS, SOURCE_METHOD);
+        logger.entering(SOURCE_CLASS, SOURCE_METHOD, PARAM_1);
+        logger.entering(SOURCE_CLASS, SOURCE_METHOD, PARAMS);
+        logger.exiting(SOURCE_CLASS, SOURCE_METHOD);
+        logger.exiting(SOURCE_CLASS, SOURCE_METHOD, PARAM_1);
+        logger.throwing(SOURCE_CLASS, SOURCE_METHOD, new RuntimeException());
         messages = app.getMessages();
-        assertEquals("Incorrect number of messages.", 6, messages.size());
-        for (final String message : messages) {
-            assertEquals("Incorrect caller class name.", SOURCE_CLASS, 
message);
+        assertMessageCount(12, messages);
+        for (int i = 0; i < messages.size(); i++) {
+            String message = messages.get(i);
+            assertEquals("Incorrect caller class name for message " + i, 
SOURCE_CLASS, message);
         }
     }
 
@@ -81,15 +138,32 @@ public class CallerInformationTest {
     public void testMethodLogger() throws Exception {
         final ListAppender app = ctx.getListAppender("Method").clear();
         final Logger logger = Logger.getLogger("MethodLogger");
-        logger.info("More messages.");
-        logger.warning("CATASTROPHE INCOMING!");
-        logger.severe("ZOMBIES!!!");
-        logger.warning("brains~~~");
-        logger.info("Itchy. Tasty.");
+        // Eager methods
+        logger.severe("CATASTROPHE INCOMING!");
+        logger.warning("ZOMBIES!!!");
+        logger.info("brains~~~");
+        logger.config("Config!");
+        logger.fine("Itchy. Tasty.");
+        logger.finer("Finer message.");
+        logger.finest("Finest message.");
+        logger.log(Level.FINEST, "Finest message.");
+        logger.log(Level.FINEST, "Message of level {1}.", Level.FINEST);
+        logger.log(Level.FINEST, "Hello {1} and {2}!.", new Object[] {"foo", 
"bar"});
+        // Lazy methods
+        logger.severe(() -> "CATASTROPHE INCOMING!");
+        logger.warning(() -> "ZOMBIES!!!");
+        logger.info(() -> "brains~~~");
+        logger.config(() -> "Config!");
+        logger.fine(() -> "Itchy. Tasty.");
+        logger.finer(() -> "Finer message.");
+        logger.finest(() -> "Finest message.");
+        logger.log(Level.FINEST, () -> "Finest message.");
+        logger.log(Level.FINEST, new RuntimeException(), () -> "Message with 
exception.");
         List<String> messages = app.getMessages();
-        assertEquals("Incorrect number of messages.", 5, messages.size());
-        for (final String message : messages) {
-            assertEquals("Incorrect caller method name.", "testMethodLogger", 
message);
+        assertMessageCount(19, messages);
+        for (int i = 0; i < messages.size(); i++) {
+            String message = messages.get(i);
+            assertEquals("Incorrect caller class name for message " + i, 
"testMethodLogger", message);
         }
 
         // Test passing the location information directly
@@ -100,10 +174,35 @@ public class CallerInformationTest {
         logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello!", new 
RuntimeException());
         logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, () -> "Hello " + 
PARAM_1 + "!");
         logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, new 
RuntimeException(), () -> "Hello " + PARAM_1 + "!");
+        logger.entering(SOURCE_CLASS, SOURCE_METHOD);
+        logger.entering(SOURCE_CLASS, SOURCE_METHOD, PARAM_1);
+        logger.entering(SOURCE_CLASS, SOURCE_METHOD, PARAMS);
+        logger.exiting(SOURCE_CLASS, SOURCE_METHOD);
+        logger.exiting(SOURCE_CLASS, SOURCE_METHOD, PARAM_1);
+        logger.throwing(SOURCE_CLASS, SOURCE_METHOD, new RuntimeException());
         messages = app.getMessages();
-        assertEquals("Incorrect number of messages.", 6, messages.size());
-        for (final String message : messages) {
-            assertEquals("Incorrect caller method name.", SOURCE_METHOD, 
message);
+        assertMessageCount(12, messages);
+        for (int i = 0; i < messages.size(); i++) {
+            String message = messages.get(i);
+            assertEquals("Incorrect caller class name for message " + i, 
SOURCE_METHOD, message);
+        }
+    }
+
+    private void assertMessageCount(int expectedCount, List<String> messages) {
+        assertEquals("Incorrect number of messages.", expectedCount, 
messages.size());
+        if (filter != null) {
+            assertEquals("Incorrect number of filtered messages.", 
expectedCount, filter.count.getAndSet(0));
+        }
+    }
+
+    private static final class CountingFilter implements Filter {
+
+        AtomicInteger count = new AtomicInteger();
+
+        @Override
+        public boolean isLoggable(LogRecord record) {
+            count.incrementAndGet();
+            return true;
         }
     }
 }
diff --git 
a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CoreLoggerTest.java 
b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CoreLoggerTest.java
deleted file mode 100644
index 57d63b9372..0000000000
--- 
a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CoreLoggerTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.jul.test;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import org.apache.logging.log4j.core.test.appender.ListAppender;
-import org.apache.logging.log4j.jul.LogManager;
-import org.apache.logging.log4j.util.Strings;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class CoreLoggerTest extends AbstractLoggerTest {
-
-    @BeforeClass
-    public static void setUpClass() {
-        System.setProperty("java.util.logging.manager", 
LogManager.class.getName());
-    }
-
-    @AfterClass
-    public static void tearDownClass() {
-        System.clearProperty("java.util.logging.manager");
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        logger = Logger.getLogger(LOGGER_NAME);
-        logger.setFilter(null);
-        assertThat(logger.getLevel(), equalTo(Level.FINE));
-        eventAppender = ListAppender.getListAppender("TestAppender");
-        flowAppender = ListAppender.getListAppender("FlowAppender");
-        stringAppender = ListAppender.getListAppender("StringAppender");
-        assertNotNull(eventAppender);
-        assertNotNull(flowAppender);
-        assertNotNull(stringAppender);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (eventAppender != null) {
-            eventAppender.clear();
-        }
-        if (flowAppender != null) {
-            flowAppender.clear();
-        }
-        if (stringAppender != null) {
-            stringAppender.clear();
-        }
-    }
-
-    @Test
-    public void testRootSetLevelToNull() throws Exception {
-        final Logger rootLogger = Logger.getLogger(Strings.EMPTY);
-        assertThat(rootLogger.getLevel(), equalTo(Level.SEVERE));
-        assertThat(rootLogger.isLoggable(Level.SEVERE), is(true));
-        // null test
-        rootLogger.setLevel(null);
-        assertThat(rootLogger.getLevel(), equalTo(null));
-        assertThat(rootLogger.isLoggable(Level.SEVERE), is(true));
-        // now go back to a different one
-        rootLogger.setLevel(Level.INFO);
-        assertThat(rootLogger.getLevel(), equalTo(Level.INFO));
-        assertThat(rootLogger.isLoggable(Level.FINE), is(false));
-    }
-
-    @Test
-    public void testSetLevel() throws Exception {
-        final Logger childLogger = Logger.getLogger(LOGGER_NAME + ".Child");
-        assertThat(childLogger.getLevel(), equalTo(Level.FINE));
-        logger.setLevel(Level.SEVERE);
-        assertThat(childLogger.getLevel(), equalTo(Level.FINE));
-        assertThat(logger.getLevel(), equalTo(Level.SEVERE));
-        logger.setLevel(Level.FINER);
-        assertThat(logger.getLevel(), equalTo(Level.FINER));
-        logger.setLevel(Level.FINE);
-        assertThat(logger.getLevel(), equalTo(Level.FINE));
-        assertThat(childLogger.getLevel(), equalTo(Level.FINE));
-        assertThat(childLogger.isLoggable(Level.ALL), is(false));
-    }
-
-    @Test
-    public void testSetLevelToNull() throws Exception {
-        final Logger childLogger = Logger.getLogger(LOGGER_NAME + 
".NullChild");
-        assertThat(childLogger.getLevel(), equalTo(Level.FINE));
-        assertThat(childLogger.isLoggable(Level.FINE), is(true));
-        childLogger.setLevel(Level.SEVERE);
-        assertThat(childLogger.getLevel(), equalTo(Level.SEVERE));
-        assertThat(childLogger.isLoggable(Level.FINE), is(false));
-        // null test
-        childLogger.setLevel(null);
-        assertThat(childLogger.getLevel(), equalTo(null));
-        assertThat(childLogger.isLoggable(Level.FINE), is(true));
-        // now go back
-        childLogger.setLevel(Level.SEVERE);
-        assertThat(childLogger.getLevel(), equalTo(Level.SEVERE));
-        assertThat(childLogger.isLoggable(Level.FINE), is(false));
-    }
-}
diff --git 
a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CustomLoggerAdapterTest.java
 
b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CustomLoggerAdapterTest.java
new file mode 100644
index 0000000000..b1d11aa4b7
--- /dev/null
+++ 
b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CustomLoggerAdapterTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.jul.test;
+
+import static 
org.apache.logging.log4j.jul.test.JulTestProperties.JUL_LOGGER_ADAPTER;
+import static org.junit.Assert.assertTrue;
+
+import java.util.logging.Logger;
+import org.apache.logging.log4j.jul.AbstractLoggerAdapter;
+import org.apache.logging.log4j.jul.ApiLogger;
+import org.apache.logging.log4j.jul.LogManager;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+import org.apache.logging.log4j.spi.LoggerContext;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests if the logger adapter can be customized.
+ */
+public class CustomLoggerAdapterTest {
+
+    @BeforeClass
+    public static void setUpClass() {
+        System.setProperty("java.util.logging.manager", 
LogManager.class.getName());
+        System.setProperty(JUL_LOGGER_ADAPTER, 
CustomLoggerAdapter.class.getName());
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+        System.clearProperty("java.util.logging.manager");
+        System.clearProperty(JUL_LOGGER_ADAPTER);
+    }
+
+    @Test
+    public void testCustomLoggerAdapter() {
+        Logger logger = 
Logger.getLogger(CustomLoggerAdapterTest.class.getName());
+        assertTrue("CustomLoggerAdapter is used", logger instanceof 
CustomLogger);
+    }
+
+    public static class CustomLoggerAdapter extends AbstractLoggerAdapter {
+
+        @Override
+        protected Logger newLogger(String name, LoggerContext context) {
+            return new CustomLogger(context.getLogger(name));
+        }
+    }
+
+    private static class CustomLogger extends ApiLogger {
+
+        CustomLogger(ExtendedLogger logger) {
+            super(logger);
+        }
+    }
+}
diff --git 
a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/Log4jBridgeHandlerTest.java
 
b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/Log4jBridgeHandlerTest.java
index 86425c2412..46eb50f944 100644
--- 
a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/Log4jBridgeHandlerTest.java
+++ 
b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/Log4jBridgeHandlerTest.java
@@ -18,7 +18,6 @@ package org.apache.logging.log4j.jul.test;
 
 // note: NO import of Logger, Level, LogManager to prevent conflicts JUL/log4j
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
@@ -29,6 +28,7 @@ import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.Configurator;
 import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.assertj.core.api.Assertions;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -112,7 +112,7 @@ public class Log4jBridgeHandlerTest {
         if (OUTPUT_CAPTURED) prevSysErrStream.print(logOutput);
         logOutput = logOutput.replace("\r\n", "\n");
         regex = regex + "(.|\\n)*"; // allow any text with NL afterwards
-        assertTrue("Unmatching output:\n" + logOutput + "\n-- vs: --\n" + 
regex + "\n----", logOutput.matches(regex));
+        Assertions.assertThat(logOutput).matches(regex);
     }
 
     /** Get regex for a JUL console output. Must match JUL-Console-Formatter! 
*/
@@ -307,62 +307,6 @@ public class Log4jBridgeHandlerTest {
         assertEquals("Logger '" + loggerName + "'", julLevel, (lg == null ? 
null : lg.getLevel()));
     }
 
-    @Test
-    public void test5LevelPropFromConfigFile() {
-        // JUL levels are set from config files and the initial propagation
-        assertLogLevel("", java.util.logging.Level.FINE);
-        assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1", 
java.util.logging.Level.FINE);
-        assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested1", 
java.util.logging.Level.FINER);
-        
assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested2.deeplyNested", 
java.util.logging.Level.WARNING);
-        assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate2", 
java.util.logging.Level.ALL);
-        
assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate2.nested.deeplyNested", 
java.util.logging.Level.INFO);
-        // these are set in logging.properties but not in log4j2.xml:
-        assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate2.nested", null);
-        assertLogLevel("javax.mail", null);
-        // these should not exist:
-        assertLogLevel("log4j.Log4jBridgeHandlerTest", null);
-        assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested", null);
-        
assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested1.deeplyNested", 
null);
-    }
-
-    @Test
-    public void test5LevelPropSetLevel() {
-        String name = "log4j.test.new_logger_level_set";
-        Configurator.setLevel(name, org.apache.logging.log4j.Level.DEBUG);
-        assertLogLevel(name, java.util.logging.Level.FINE);
-        test5LevelPropFromConfigFile(); // the rest should be untouched!
-
-        name = "log4j.Log4jBridgeHandlerTest.propagate1.nested1";
-        Configurator.setLevel(name, org.apache.logging.log4j.Level.WARN);
-        assertLogLevel(name, java.util.logging.Level.WARNING);
-        // the others around should be untouched
-        assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1", 
java.util.logging.Level.FINE);
-        
assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested2.deeplyNested", 
java.util.logging.Level.WARNING);
-
-        // note: no need to check for the other set[Root]Level() methods, 
because they all call
-        // loggerContext.updateLoggers() which calls firePropertyChangeEvent()
-    }
-
-    @Test
-    public void test5LevelPropGC() {
-        // this test will fail if you comment out "julLoggerRefs.add(julLog);" 
in propagateLogLevels()
-        test5LevelPropFromConfigFile(); // at start, all should be fine
-        final java.util.logging.Logger julLogRef =
-                
java.util.logging.Logger.getLogger("log4j.Log4jBridgeHandlerTest.propagate1.nested1");
-        System.gc(); // a single call is sufficient
-        System.out.println("sysout:  test5LevelPropGC() still has reference to 
JUL-logger: " + julLogRef.getName()
-                + " / " + julLogRef);
-        try {
-            test5LevelPropFromConfigFile(); // even after GC the not 
referenced loggers should still be there
-        } catch (Throwable t) {
-            debugPrintJulLoggers("After GC");
-            // => JUL root logger, above explicitly referenced logger and its 
parent ("...propagate1")
-            //    and the global referenced julLog 
("...jul.Log4jBridgeHandlerTest") are still there, the
-            //    others are null-references now
-            throw t;
-        }
-    }
-
     /** Print all available JUL loggers to stdout. */
     private static void debugPrintJulLoggers(final String infoStr) {
         final java.util.logging.LogManager julMgr = 
java.util.logging.LogManager.getLogManager();
diff --git a/log4j-jul/src/test/resources/CallerInformationTest.xml 
b/log4j-jul/src/test/resources/CallerInformationTest.xml
index 4a73dddcc7..087587ea5d 100644
--- a/log4j-jul/src/test/resources/CallerInformationTest.xml
+++ b/log4j-jul/src/test/resources/CallerInformationTest.xml
@@ -31,10 +31,10 @@
     </List>
   </Appenders>
   <Loggers>
-    <Logger name="ClassLogger" level="INFO">
+    <Logger name="ClassLogger" level="ALL">
       <AppenderRef ref="Class"/>
     </Logger>
-    <Logger name="MethodLogger" level="INFO">
+    <Logger name="MethodLogger" level="ALL">
       <AppenderRef ref="Method"/>
     </Logger>
     <Root level="OFF"/>
diff --git a/log4j-jul/src/test/resources/log4j2-julBridge-test.xml 
b/log4j-jul/src/test/resources/log4j2-julBridge-test.xml
index 18fb678c56..15cd5a5d4c 100644
--- a/log4j-jul/src/test/resources/log4j2-julBridge-test.xml
+++ b/log4j-jul/src/test/resources/log4j2-julBridge-test.xml
@@ -15,25 +15,20 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<Configuration status="info">
+<Configuration xmlns="https://logging.apache.org/xml/ns";
+               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+               xsi:schemaLocation="
+                   https://logging.apache.org/xml/ns
+                   https://logging.apache.org/xml/ns/log4j-config-3.xsd";>
     <Appenders>
         <Console name="STDOUT" target="SYSTEM_ERR" follow="true">    <!-- 
syserr + follow !! -->
-            <PatternLayout pattern="log4j2:  %d{HH:mm:ss.SSS} %5level - 
[%thread][%logger: %class/%method/%line]  -  %message%n" />
+            <PatternLayout
+                    pattern="log4j2: %d{HH:mm:ss.SSS} %5level - 
[%thread][%logger: %class/%method/%line] - %message%n"/>
         </Console>
     </Appenders>
-
     <Loggers>
-        <Root level="DEBUG">
-            <AppenderRef ref="STDOUT" />
+        <Root level="TRACE">
+            <AppenderRef ref="STDOUT"/>
         </Root>
-
-        <!-- needs to be set to a lower level: -->
-        <Logger 
name="org.apache.logging.log4j.jul.test.Log4jBridgeHandlerTest" level="TRACE" />
-        <!-- some test configs: -->
-        <Logger name="log4j.Log4jBridgeHandlerTest.propagate1" level="DEBUG" />
-        <Logger name="log4j.Log4jBridgeHandlerTest.propagate1.nested1" 
level="TRACE" />
-        <Logger 
name="log4j.Log4jBridgeHandlerTest.propagate1.nested2.deeplyNested" 
level="WARN" />
-        <Logger name="log4j.Log4jBridgeHandlerTest.propagate2" level="ALL" />
-        <Logger 
name="log4j.Log4jBridgeHandlerTest.propagate2.nested.deeplyNested" level="INFO" 
/>
     </Loggers>
 </Configuration>
diff --git a/log4j-jul/src/test/resources/logging-test.properties 
b/log4j-jul/src/test/resources/logging-test.properties
index 9a0a4756c7..4b1755391a 100644
--- a/log4j-jul/src/test/resources/logging-test.properties
+++ b/log4j-jul/src/test/resources/logging-test.properties
@@ -29,19 +29,10 @@ 
org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels = true
 # ConsoleHandler defaults to INFO filtering, but we need all here
 java.util.logging.ConsoleHandler.level = ALL
 java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
-java.util.logging.SimpleFormatter.format = JUL:  %1$tT.%1$tL %4$s [%3$s: %2$s] 
 -  %5$s%6$s%n
-
-
-# note: JUL levels are  SEVERE, WARNING, INFO, FINE, FINER, FINEST, ALL
+java.util.logging.SimpleFormatter.format = JUL: %1$tT.%1$tL %4$s [%3$s: %2$s] 
- %5$s%6$s%n
 
+# note: JUL levels are: SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST, ALL
 # set default JUL logging level (FINER is for entering, exiting etc.)
-# out-comment to use default of "INFO" - will be set by level propagation to 
DEBUG=FINE again
-#.level = FINE
-org.apache.logging.log4j.jul.Log4jBridgeHandlerTest.level = FINER
+org.apache.logging.log4j.jul.test.Log4jBridgeHandlerTest.level = FINER
 # do not log mail-init. (is done on INFO-level) because this would init. JUL 
before setErr() happens
 javax.mail.level = WARNING
-
-# configure (initial) JUL levels differently to log4j-config (and use high 
levels here)
-log4j.Log4jBridgeHandlerTest.propagate1.nested1.level = SEVERE
-# this is a logger not directly available in log4j, but the level above and 
below is defined in log4j:
-log4j.Log4jBridgeHandlerTest.propagate2.nested.level = WARNING
diff --git a/pom.xml b/pom.xml
index a4b354f9b2..f1c1ce0ec5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -264,6 +264,7 @@
     <module>log4j-spring-cloud-config-client</module>
     <module>log4j-to-jul</module>
     <module>log4j-to-slf4j</module>
+    <module>log4j-jul-propagator</module>
 
   </modules>
 
diff --git a/src/site/antora/modules/ROOT/pages/log4j-jul.adoc 
b/src/site/antora/modules/ROOT/pages/log4j-jul.adoc
index 854b102c38..c5b7d482ac 100644
--- a/src/site/antora/modules/ROOT/pages/log4j-jul.adoc
+++ b/src/site/antora/modules/ROOT/pages/log4j-jul.adoc
@@ -15,116 +15,175 @@ Licensed to the Apache Software Foundation (ASF) under 
one or more
     limitations under the License.
 ////
 
-= Log4j JDK Logging Adapter
+= JUL-to-Log4j bridge
 
-The JDK Logging Adapter is a custom implementation of
-https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/LogManager.html[`java.util.logging.LogManager`]
-that uses link:javadoc/log4j-core/index.html[Log4j].
-This adapter can be used with either the Log4j API or Log4j Core.
-When used with the API, there are a couple features of JUL that aren't 
supported.
-However, this does allow any other Log4j Provider besides the Core provider to 
be used with JUL.
+The JUL-to-Log4j bridge provides components that allow application and library 
that use
+https://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html[`java.util.logging.Logger`]
+(JUL) to log to the Log4j API instead.
 
-== Requirements
+[IMPORTANT]
+====
+This chapter covers advanced usage scenarios of the JUL-to-Log4j bridge.
+For the installation procedure and basic configuration see
+xref:manual/installation.adoc#impl-core-bridge-jul[Using JUL-to-Log4j]
+section of our
+xref:manual/installation.adoc[Installation guide].
+====
 
-The JDK Logging Adapter is dependent on the Log4j API and optionally Log4j 
Core.
+[#configuration]
+== Configuration
 
-== Usage
+.Struggling with the logging API, implementation, and bridge concepts? Click 
for an introduction.
+[%collapsible]
+====
+include::partial$concepts.adoc[tag=!software-type]
+====
 
-To use the JDK Logging Adapter, you must set the system property 
`java.util.logging.manager` to `org.apache.logging.log4j.jul.LogManager`.
+The `java.util.logging` logging API, available since JRE 1.4, shares many 
similarities with other logging API, such as SLF4J or Log4j API.
+Similarly to other APIs, it allows users to change the underlying
+https://docs.oracle.com/javase/8/docs/api/java/util/logging/LogManager.html[`LogManager`]
+implementation, but unlike other APIs, it has two big limitations:
 
-This must be done either through the command line (i.e., using the 
`-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager` argument) 
or by using `System.setProperty()` before any calls are made to `LogManager` or 
`Logger`.
+* it is part of JRE, which means that each JVM can contain only one instance 
of the `LogManager` class and all the applications of an application server 
must use the same `LogManager` implementation,
+* it does not support auto-detection of the logging backend through 
`ServiceLoader` or a similar mechanim (see
+https://bugs.openjdk.org/browse/JDK-8262741[JDK-8262741]
+).
+In order to switch to an alternate `LogManager` implementation you must be 
able to set the `java.util.logging.manager` system property **before** the 
first logging call.
 
-== Compatibility
+To work around the limitations of JUL, the JUL-to-Log4j bridge offers two 
installation options:
+
+. If you are able to modify the `java.util.logging.manager` system property 
very early in the JVM startup process, you can replace the default `LogManager` 
implementation with a Log4j-specific one.
+This option gives the best performance.
+See <<bridge-logmanager>> for details.
+. If JUL initializes **before** your application does, which is a typical 
behavior in application servers, you can still configure JUL to use Log4j as 
appender.
+See <<bridge-handler>> for details.
+
+[#bridge-logmanager]
+== Using `LogManager`
+
+The best way to install the JUL-to-Log4j bridge on your system is to set the 
value of the `java.util.logging.manager` Java system property to
+
+----
+org.apache.logging.log4j.jul.LogManager
+----
+
+This property must be set very early in an application initialization process, 
e.g. using the `-D<property>=<value>` command line option of the `java` 
executable or by adding:
+
+[source,java]
+----
+static {
+  if (System.getProperty("java.util.logging.manager") == null) {
+    System.setProperty("java.util.logging.manager", 
"org.apache.logging.log4j.jul.LogManager");
+  }
+}
+----
+
+at the top of your main class.
+
+Setting this property will replace the default JUL `LogManager` implementation 
with a custom implementation that translates JUL `Logger` method calls into 
Log4j `Logger` calls with a **minimal** overhead.
+
+[#bridge-logmanager-features]
+=== `LogManager`-specific features
 
 The use of a
-https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Filter.html[`java.util.logging.Filter`]
+https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Filter.html[`java.util.logging.Filter`]
 is supported on a
-per-link:https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Logger.html[`Logger`]
+https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Logger.html[per-`Logger`]
 basis.
-However, it is recommended to use the standard 
xref:manual/filters.adoc[filters] feature in Log4j instead.
+However, it is recommended to use the standard 
xref:manual/filters.adoc[Filters] feature in Log4j instead.
 
 The use of
-https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Handler.html[`java.util.logging.Handler`]
-classes is
-_NOT_ supported.
-Custom Handlers should instead use an appropriate
-xref:manual/appenders.adoc[Appender]
-or code their own
-link:../javadoc/log4j-core/org/apache/logging/log4j/core/Appender.html[`Appender`]
-plugin.
-
-Java logging levels are translated into Log4j logging levels dynamically.
-The following table lists the conversions between a Java logging level and its 
equivalent Log4j level.
-Custom levels should be implemented as an implementation of `LevelConverter`, 
and the Log4j property
-xref:manual/systemproperties.adoc#log4j.jul.levelConverter[`log4j.jul.levelConverter`]
-must be set to your custom class name.
-Using the default `LevelConverter` implementation, custom logging levels are 
mapped to whatever the current level of the `Logger` being logged to is using.
-
-[#default-level-conversions]
-=== Default Level Conversions
-
-.JUL to Log4j level conversion
-[%header]
-|===
-| Java Level | Log4j Level
+https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Handler.html[`java.util.logging.Handler`]
+classes is **not** supported.
+Custom handlers should be replaced with the appropriate
+xref:manual/appenders.adoc[Log4j Appender].
 
-| 
https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#OFF[`OFF`]
-| {log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/Level.html#OFF[`OFF`]
+[#bridge-handler]
+== Using `Log4jBridgeHandler`
 
-| 
https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#SEVERE[`SEVERE`]
-| 
{log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/Level.html#ERROR[`ERROR`]
+.Are you a Spring Boot user?
+[TIP]
+====
+Spring Boot will automatically configure `Log4jBridgeHandler`.
+====
 
-| 
https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#WARNING[`WARNING`]
-| 
{log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/Level.html#WARN[`WARN`]
+If setting the `java.util.logging.manager` system property is not possible, 
the JUL-to-Log4j bridge offers an implementation of JUL's
+https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Handler.html[`Handler`]
+abstract class, which redirects all log events to Log4j Core:
+`org.apache.logging.log4j.jul.Log4jBridgeHandler`.
 
-| 
https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#OFF[`OFF`]
-| 
{log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/Level.html#INFO[`INFO`]
+[WARNING]
+====
+The `Log4jBridgeHandler` requires Log4j Core as logging implementation and 
will fail with other Log4j API implementations.
+====
 
-| 
https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#CONFIG[`CONFIG`]
-| custom `CONFIG` level
+In order to use `Log4jBridgeHandler` you can either:
 
-| 
https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#FINE[`FINE`]
-| 
{log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/Level.html#DEBUG[`DEBUG`]
+* modify the default JUL configuration file `logging.properties` to only 
contain:
++
+[source,properties]
+----
+# Set Log4jBridgeHandler as only handler for all JUL loggers
+handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler
+----
++
+See the
+https://docs.oracle.com/javase/8/docs/api/java/util/logging/LogManager.html[JRE
 documentation]
+for details about the format and location of the `logging.properties` file.
 
-| 
https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#FINER[`FINER`]
-| 
{log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/Level.html#TRACE[`TRACE`]
+* or call the `Log4jBridgeHandler.install()` method in your code.
 
-| 
https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#FINEST[`FINEST`]
-| custom `FINEST` level
+[IMPORTANT]
+====
+Usage of `Log4jBridgeHandler` introduces a considerably higher overhead that 
the usage of `LogManager`, since logging events need to traverse the entire JUL 
logging pipeline followed by the logging pipeline of the Log4j API 
implementation.
 
-| 
https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#ALL[`ALL`]
-| {log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/Level.html#ALL[`ALL`]
-|===
+Consider setting <<#bridge-handler-propagateLevels>> to `true` to reduce the 
overhead.
+====
+
+[#bridge-handler-propagator]
+=== Level propagators
+
+[#bridge-handler-config]
+=== `Log4jBridgeHandler` configuration options
 
-== Log4j JDK Logging Bridge Handler
+You can tune the behavior of `Log4jBridgeHandler` by adding the following 
properties to the `logging.properties` configuration file, which are also 
available as parameters to the `install()` method call:
 
-The LogManager is not always useable because you have to set a JVM wide 
effective system property - e.g.
-in web servers this is not possible if you are not the administrator.
+[#bridge-handler-appendSuffix]
+=== `appendSuffix`
 
-The `Log4jBridgeHandler` is an alternative that can be declaratively used via 
`logging.properties`.
+[cols="2h,5"]
+|===
+| Property name         | 
`org.apache.logging.log4j.jul.Log4jBridgeHandler.appendSuffix`
+| `install()` parameter | `suffixToAppend`
+| Type                  | `String`
+| Default value         | `null`
+|===
 
-It is less performant than the LogManager but still okay to use: the 
LogManager replaces the JDK implementation, so your logging code (using JDK 
syntax) effectively directly uses log4j.
-When using the BridgeHandler the original JDK implementation along with its 
configuration (e.g.
-log levels) is still fully working but the log events are "written" via this 
handler to log4j as if you would have called log4j.Logger.debug() etc.;
-it is like a FileHandler but instead of writing to a file, it "writes" to 
log4j Loggers - thus there is some overhead compared to using LogManager.
+Specifies the suffix to append to the name of all JUL loggers, which allows to 
differentiate JUL log messages from native Log4j API messages.
 
-== Usage
+[#bridge-handler-propagateLevels]
+=== `propagateLevels`
 
-The JUL configuration file `logging.properties` needs the line
+[cols="2h,5"]
+|===
+| Property name         | 
`org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels`
+| `install()` parameter | `propagateLevels`
+| Type                  | `boolean`
+| Default value         | `false`
+|===
 
-`handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler`
+The additional overhead of `Log4jBridgeHandler` can be especially heavy for 
**disabled** log statements.
+This is why you must ensure that log event filtering of the Log4j 
implementation and JUL are aligned.
+You can do it by either:
 
-and JUL logs go to log4j2.
-Additionally, you typically want to use to following:
+* configuring JUL loggers with the same levels as the Log4j loggers,
+* or setting this property to `true`, which will perform the synchronization 
automatically.
 
-`org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels = true`
+[#common-configuration]
+== Common configuration
 
-In a webapp on Tomcat (and maybe other servers, too), you may simply create a 
`WEB-INF/classes/logging.properties` file with above content.
-The bridge and the log levels defined in this file are only valid for your 
webapp and do _not_ have any impact on the other webapps on the same Tomcat 
instance.
+Independently of the way you install the JUL-to-Log4j bridge, you can finely 
tune the behavior of the bridge using the following configuration properties.
+See xref:manual/systemproperties.adoc[] for more details.
 
-Alternatively you may call `Log4jBridgeHandler.install()` inside your webapp's 
initialization code, e.g.
-inside `ServletContextListener` or a `ServletFilter` static-class-init.
-or `contextInitialized()`.
+include::partial$manual/systemproperties/properties-log4j-jul.adoc[leveloffset=+2]
 
-IMPORTANT: Log levels of JDK should match the ones of log4j.
-You may do this manually or use the automatic level propagation via 
`Log4jBridgeHandler.propagateLevels = true`.
diff --git 
a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-log4j-jul.adoc
 
b/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-log4j-jul.adoc
index d7949a889b..08b22a2811 100644
--- 
a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-log4j-jul.adoc
+++ 
b/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-log4j-jul.adoc
@@ -26,7 +26,41 @@
 
 Fully qualified name of an alternative 
`org.apache.logging.log4j.jul.LevelConverter` implementation.
 
-See xref:log4j-jul.adoc#default-level-conversions[Default Level Conversions] 
for the default implementation.
+.Default level conversions
+[%collapsible]
+====
+[cols="1m,1",id=default-level-conversions]
+|===
+| Java Level | Log4j Level
+
+| 
https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#OFF[OFF]
+| `OFF`
+
+| 
https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#SEVERE[SEVERE]
+| `ERROR`
+
+| 
https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#WARNING[WARNING]
+| `WARN`
+
+| 
https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#INFO[INFO]
+| `INFO`
+
+| 
https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#CONFIG[CONFIG]
+| custom `CONFIG` level with a numeric value of `450`
+
+| 
https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#FINE[FINE]
+| `DEBUG`
+
+| 
https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#FINER[FINER]
+| `TRACE`
+
+| 
https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#FINEST[FINEST]
+| custom `FINEST` level with a numeric value of `700`
+
+| 
https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#ALL[ALL]
+| `ALL`
+|===
+====
 
 [id=log4j.jul.loggerAdapter]
 == `log4j.jul.loggerAdapter`
@@ -35,17 +69,9 @@ See xref:log4j-jul.adoc#default-level-conversions[Default 
Level Conversions] for
 |===
 | Env. variable | `LOG4J_JUL_LOGGER_ADAPTER`
 | Type          | `Class<? extends AbstractLoggerAdapter>`
-| Default value | _depends on classpath_
+| Default value | `org.apache.logging.log4j.jul.ApiLoggerAdapter`
 |===
 
-Fully qualified class name of the 
`org.apache.logging.log4j.jul.AbstractLoggerAdapter` implementation to use.
-
-This property allows users to choose between two implementation of the logging 
bridge:
-
-org.apache.logging.log4j.jul.CoreLoggerAdapter::
-The default if `log4j-core` is found in the class path.
-It allows users to modify the Log4j Core configuration through the JUL 
https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Logger.html[`Logger`]
 interface.
+Fully qualified class name of a custom 
`org.apache.logging.log4j.jul.AbstractLoggerAdapter` implementation to use, 
which decide how to handle the level mutator methods in the JUL `Logger` 
interface.
 
-org.apache.logging.log4j.jul.ApiLoggerAdapter::
-The default if `log4j-core` cannot be found in the class path.
-It disables the level mutators in the JUL `Logger` interface.
\ No newline at end of file
+By default `org.apache.logging.log4j.jul.ApiLoggerAdapter` is used and the 
mutator methods are disabled.
\ No newline at end of file

Reply via email to