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

pkarwasz pushed a commit to branch feature/jul-to-log4j-api
in repository https://gitbox.apache.org/repos/asf/logging-jdk.git

commit bff1b13c21c9b6da314e5e3f995845b9e00f106d
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Tue Oct 22 18:57:22 2024 +0200

    Split new `jul-to-log4j` artifact from `log4j-jul` (#2935)
    
    This splits `log4j-jul` into two artifacts:
    
    - `jul-to-log4j` that contains a `j.u.l.LogManager` implementation, but
      does not depend on Log4j Core.
    - `log4j-jul` that contains a `j.u.l.Handler` implementation and depends
      on Log4j Core.
    
    We also update the `j.u.l.LogManager` implementation to:
    
    - implement methods introduced in Java 9,
    - remove methods deprecated in Java 9,
    - remove the support for `j.u.l.Filter`.
---
 jul-to-log4j/pom.xml                               | 133 +++++++
 .../logging/jul/tolog4j/LevelTranslator.java       |  88 ++++
 .../org/apache/logging/jul/tolog4j/LogManager.java | 104 +++++
 .../logging/jul/tolog4j/internal/ApiLogger.java    |  74 ++++
 .../jul/tolog4j/internal/ApiLoggerAdapter.java     |  39 ++
 .../tolog4j/internal/DefaultLevelConverter.java    | 129 ++++++
 .../jul/tolog4j/internal/JulProperties.java        |  27 ++
 .../logging/jul/tolog4j/internal/NoOpLogger.java   | 222 +++++++++++
 .../apache/logging/jul/tolog4j/package-info.java   |  22 +
 .../jul/tolog4j/spi/AbstractLoggerAdapter.java     |  67 ++++
 .../logging/jul/tolog4j/spi/LevelConverter.java    |  49 +++
 .../logging/jul/tolog4j/spi/package-info.java      |  25 ++
 .../jul/tolog4j/support/AbstractLogger.java        | 441 +++++++++++++++++++++
 .../logging/jul/tolog4j/support/package-info.java  |  26 ++
 .../resources/META-INF/log4j/propertyMapping.json  |  10 +
 .../jul/tolog4j/test/AsyncLoggerThreadsTest.java   |  59 +++
 .../test/BracketInNotInterpolatedMessageTest.java  |  58 +++
 .../jul/tolog4j/test/CallerInformationTest.java    | 162 ++++++++
 .../jul/tolog4j/test/JavaLevelTranslatorTest.java  |  72 ++++
 .../jul/tolog4j/test/JulTestProperties.java        |  24 ++
 .../jul/tolog4j/test/Log4jLevelTranslatorTest.java |  65 +++
 .../jul/tolog4j/test/ResourceBundleTest.java       |  91 +++++
 .../jul/tolog4j/test/internal/ApiLoggerTest.java   |  91 +++++
 .../DefaultLevelConverterCustomJulLevelsTest.java  | 144 +++++++
 .../test/internal/DefaultLevelConverterTest.java   |  33 ++
 .../tolog4j/test/support/AbstractLoggerTest.java   | 185 +++++++++
 .../test/support/CustomLoggerAdapterTest.java      |  95 +++++
 .../src/test/resources/CallerInformationTest.xml   |  42 ++
 .../test/resources/ResourceBundleTest.properties   |  21 +
 .../src/test/resources/ResourceBundleTest.xml      |  31 ++
 jul-to-log4j/src/test/resources/log4j2-test.xml    |  46 +++
 31 files changed, 2675 insertions(+)

diff --git a/jul-to-log4j/pom.xml b/jul-to-log4j/pom.xml
new file mode 100644
index 0000000..9e18420
--- /dev/null
+++ b/jul-to-log4j/pom.xml
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.logging.log4j</groupId>
+    <artifactId>log4j</artifactId>
+    <version>${revision}</version>
+    <relativePath>../log4j-parent</relativePath>
+  </parent>
+
+  <artifactId>jul-to-log4j</artifactId>
+  <name>Apache Log4j JUL LogManager</name>
+  <description>A `java.util.logging` LogManager that forwards events to the 
Log4j API.</description>
+
+  <properties>
+    <!--
+      ~ OSGi and JPMS options
+      -->
+    <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>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-api</artifactId>
+    </dependency>
+
+    <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>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-async-logger</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-core-test</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>
+          <!-- The `surefire-junit-platform` provider initializes JUL before 
tests start -->
+          <dependency>
+            <groupId>org.apache.maven.surefire</groupId>
+            <artifactId>surefire-junit47</artifactId>
+            <version>${surefire.version}</version>
+          </dependency>
+        </dependencies>
+        <executions>
+          <execution>
+            <id>default-test</id>
+            <goals>
+              <goal>test</goal>
+            </goals>
+            <phase>test</phase>
+            <configuration>
+              <!-- Use custom `j.u.l.LogManager` -->
+              <systemPropertyVariables>
+                
<java.util.logging.manager>org.apache.logging.jul.tolog4j.LogManager</java.util.logging.manager>
+              </systemPropertyVariables>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git 
a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/LevelTranslator.java
 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/LevelTranslator.java
new file mode 100644
index 0000000..142b19f
--- /dev/null
+++ 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/LevelTranslator.java
@@ -0,0 +1,88 @@
+/*
+ * 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.jul.tolog4j;
+
+import org.apache.logging.jul.tolog4j.internal.DefaultLevelConverter;
+import org.apache.logging.jul.tolog4j.internal.JulProperties;
+import org.apache.logging.jul.tolog4j.spi.LevelConverter;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
+
+/**
+ * Utility class to convert between JDK Levels and Log4j 2 Levels.
+ *
+ * @since 2.1
+ */
+public final class LevelTranslator {
+
+    /**
+     * Custom Log4j level corresponding to the {@link 
java.util.logging.Level#FINEST} logging level. This maps to a
+     * level more specific than {@link org.apache.logging.log4j.Level#TRACE}.
+     */
+    public static final Level FINEST = Level.forName("FINEST", 
Level.TRACE.intLevel() + 100);
+
+    /**
+     * Custom Log4j level corresponding to the {@link 
java.util.logging.Level#CONFIG} logging level. This maps to a
+     * level in between {@link org.apache.logging.log4j.Level#INFO} and {@link 
org.apache.logging.log4j.Level#DEBUG}.
+     */
+    public static final Level CONFIG = Level.forName("CONFIG", 
Level.INFO.intLevel() + 50);
+
+    private static final Logger LOGGER = StatusLogger.getLogger();
+    private static final LevelConverter LEVEL_CONVERTER;
+
+    static {
+        final Class<? extends LevelConverter> levelConverterClass =
+                
PropertyEnvironment.getGlobal().getProperty(JulProperties.class).levelConverter();
+        if (levelConverterClass != null) {
+            LevelConverter levelConverter;
+            try {
+                levelConverter = LoaderUtil.newInstanceOf(levelConverterClass);
+            } catch (final Exception e) {
+                LOGGER.error("Could not create custom LevelConverter [{}].", 
levelConverterClass.getName(), e);
+                levelConverter = new DefaultLevelConverter();
+            }
+            LEVEL_CONVERTER = levelConverter;
+        } else {
+            LEVEL_CONVERTER = new DefaultLevelConverter();
+        }
+    }
+
+    /**
+     * Converts a JDK logging Level to a Log4j logging Level.
+     *
+     * @param level JDK Level to convert, may be null per the JUL 
specification.
+     * @return converted Level or null
+     */
+    public static Level toLevel(final java.util.logging.Level level) {
+        return LEVEL_CONVERTER.toLevel(level);
+    }
+
+    /**
+     * Converts a Log4j logging Level to a JDK logging Level.
+     *
+     * @param level Log4j Level to convert.
+     * @return converted Level.
+     */
+    public static java.util.logging.Level toJavaLevel(final Level level) {
+        return LEVEL_CONVERTER.toJavaLevel(level);
+    }
+
+    private LevelTranslator() {}
+}
diff --git 
a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/LogManager.java 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/LogManager.java
new file mode 100644
index 0000000..6e105aa
--- /dev/null
+++ b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/LogManager.java
@@ -0,0 +1,104 @@
+/*
+ * 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.jul.tolog4j;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Logger;
+import org.apache.logging.jul.tolog4j.internal.ApiLoggerAdapter;
+import org.apache.logging.jul.tolog4j.internal.JulProperties;
+import org.apache.logging.jul.tolog4j.internal.NoOpLogger;
+import org.apache.logging.jul.tolog4j.spi.AbstractLoggerAdapter;
+import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
+
+/**
+ * Log4j implementation of {@link java.util.logging.LogManager}.
+ * <p>
+ *      Note that the system property {@code java.util.logging.manager} must 
be set to
+ *      {@code org.apache.logging.jul.tolog4j.LogManager} in order to use this 
adaptor.
+ *      This LogManager requires the {@code log4j-api} library to be available.
+ * </p>
+ * <p>
+ *     To override the default {@link AbstractLoggerAdapter} that is used, 
specify the Log4j property
+ *     {@code log4j.jul.LoggerAdapter} and set it to the fully qualified class 
name of a custom
+ *     implementation.
+ *     All implementations must have a default constructor.
+ * </p>
+ *
+ * @since 2.1
+ */
+public class LogManager extends java.util.logging.LogManager {
+
+    private static final org.apache.logging.log4j.Logger LOGGER = 
StatusLogger.getLogger();
+    private final AbstractLoggerAdapter loggerAdapter;
+    // Contains the set of logger names that are actively being requested 
using getLogger.
+    private final ThreadLocal<Set<String>> recursive = 
ThreadLocal.withInitial(HashSet::new);
+
+    public LogManager() {
+        AbstractLoggerAdapter adapter = null;
+        final Class<? extends AbstractLoggerAdapter> adapterClass =
+                
PropertyEnvironment.getGlobal().getProperty(JulProperties.class).loggerAdapter();
+        if (adapterClass != null) {
+            try {
+                LOGGER.info("Trying to use LoggerAdapter [{}] specified by 
Log4j property.", adapterClass.getName());
+                adapter = LoaderUtil.newInstanceOf(adapterClass);
+            } catch (final Exception e) {
+                LOGGER.error(
+                        "Specified LoggerAdapter [{}] can not be created, 
using default.", adapterClass.getName(), e);
+            }
+        }
+        if (adapter == null) {
+            // Use API by default
+            // See https://github.com/apache/logging-log4j2/issues/2353
+            adapter = new ApiLoggerAdapter();
+        }
+        loggerAdapter = adapter;
+        LOGGER.info("Registered Log4j as the java.util.logging.LogManager.");
+    }
+
+    @Override
+    public boolean addLogger(final Logger logger) {
+        // in order to prevent non-bridged loggers from being registered, we 
always return false to indicate that
+        // the named logger should be obtained through getLogger(name)
+        return false;
+    }
+
+    @Override
+    public Logger getLogger(final String name) {
+        LOGGER.trace("Call to LogManager.getLogger({})", name);
+        final Set<String> activeRequests = recursive.get();
+        if (activeRequests.add(name)) {
+            try {
+                return loggerAdapter.getLogger(name);
+            } finally {
+                activeRequests.remove(name);
+            }
+        }
+        LOGGER.warn("Recursive call to getLogger for {} ignored.", name);
+        return new NoOpLogger(name);
+    }
+
+    @Override
+    public Enumeration<String> getLoggerNames() {
+        return Collections.enumeration(
+                
loggerAdapter.getLoggersInContext(loggerAdapter.getContext()).keySet());
+    }
+}
diff --git 
a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/ApiLogger.java
 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/ApiLogger.java
new file mode 100644
index 0000000..bde8a19
--- /dev/null
+++ 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/ApiLogger.java
@@ -0,0 +1,74 @@
+/*
+ * 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.jul.tolog4j.internal;
+
+import java.util.logging.Filter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.logging.jul.tolog4j.support.AbstractLogger;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * Implementation of {@link java.util.logging.Logger} that ignores all method 
calls that do not have an equivalent in
+ * the Log4j API.
+ */
+public class ApiLogger extends AbstractLogger {
+
+    private static final String MUTATOR_DISABLED =
+            """
+            Ignoring call to `j.ul.Logger.{}()`, since the Log4j API does not 
provide methods to modify the underlying implementation.
+            To modify the configuration using JUL, use an 
`AbstractLoggerAdapter` appropriate for your logging implementation.
+            See 
https://logging.apache.org/log4j/3.x/log4j-jul.html#log4j.jul.loggerAdapter for 
more information.""";
+    private static final org.apache.logging.log4j.Logger LOGGER = 
StatusLogger.getLogger();
+
+    public ApiLogger(ExtendedLogger logger) {
+        super(logger);
+    }
+
+    @Override
+    public void setFilter(Filter newFilter) throws SecurityException {
+        LOGGER.warn(MUTATOR_DISABLED, "setFilter");
+    }
+
+    @Override
+    public void setLevel(Level newLevel) throws SecurityException {
+        LOGGER.warn(MUTATOR_DISABLED, "setLevel");
+    }
+
+    @Override
+    public void addHandler(Handler handler) throws SecurityException {
+        LOGGER.warn(MUTATOR_DISABLED, "addHandler");
+    }
+
+    @Override
+    public void removeHandler(Handler handler) throws SecurityException {
+        LOGGER.warn(MUTATOR_DISABLED, "removeHandler");
+    }
+
+    @Override
+    public void setUseParentHandlers(boolean useParentHandlers) {
+        LOGGER.warn(MUTATOR_DISABLED, "setUseParentHandlers");
+    }
+
+    @Override
+    public void setParent(Logger parent) {
+        throw new UnsupportedOperationException(
+                ApiLogger.class.getSimpleName() + " does not support 
`j.u.l.Logger#setParent()`.");
+    }
+}
diff --git 
a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/ApiLoggerAdapter.java
 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/ApiLoggerAdapter.java
new file mode 100644
index 0000000..6e6778f
--- /dev/null
+++ 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/ApiLoggerAdapter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.jul.tolog4j.internal;
+
+import java.util.logging.Logger;
+import org.apache.logging.jul.tolog4j.spi.AbstractLoggerAdapter;
+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 using just log4j-api. This is the 
fallback registry used when log4j-core is
+ * not available.
+ *
+ * @since 2.1
+ */
+public class ApiLoggerAdapter extends AbstractLoggerAdapter {
+
+    private static final MessageFactory MESSAGE_FACTORY = new 
MessageFormatMessageFactory();
+
+    @Override
+    public Logger newLogger(final String name, final LoggerContext context) {
+        return new ApiLogger(context.getLogger(name, MESSAGE_FACTORY));
+    }
+}
diff --git 
a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/DefaultLevelConverter.java
 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/DefaultLevelConverter.java
new file mode 100644
index 0000000..9a8ed84
--- /dev/null
+++ 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/DefaultLevelConverter.java
@@ -0,0 +1,129 @@
+/*
+ * 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.jul.tolog4j.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.logging.jul.tolog4j.LevelTranslator;
+import org.apache.logging.jul.tolog4j.spi.LevelConverter;
+import org.apache.logging.log4j.Level;
+
+/**
+ * Default implementation of LevelConverter strategy.
+ * <p>
+ * Since 2.4, supports custom JUL levels by mapping them to their closest 
mapped neighbour.
+ * </p>
+ *
+ * @since 2.1
+ */
+public class DefaultLevelConverter implements LevelConverter {
+
+    static final class JulLevelComparator implements 
Comparator<java.util.logging.Level> {
+        @Override
+        public int compare(final java.util.logging.Level level1, final 
java.util.logging.Level level2) {
+            return Integer.compare(level1.intValue(), level2.intValue());
+        }
+    }
+
+    private final ConcurrentMap<java.util.logging.Level, Level> julToLog4j = 
new ConcurrentHashMap<>(9);
+    private final Map<Level, java.util.logging.Level> log4jToJul = new 
IdentityHashMap<>(10);
+    private final List<java.util.logging.Level> sortedJulLevels = new 
ArrayList<>(9);
+
+    public DefaultLevelConverter() {
+        // Map JUL to Log4j
+        mapJulToLog4j(java.util.logging.Level.ALL, Level.ALL);
+        mapJulToLog4j(java.util.logging.Level.FINEST, LevelTranslator.FINEST);
+        mapJulToLog4j(java.util.logging.Level.FINER, Level.TRACE);
+        mapJulToLog4j(java.util.logging.Level.FINE, Level.DEBUG);
+        mapJulToLog4j(java.util.logging.Level.CONFIG, LevelTranslator.CONFIG);
+        mapJulToLog4j(java.util.logging.Level.INFO, Level.INFO);
+        mapJulToLog4j(java.util.logging.Level.WARNING, Level.WARN);
+        mapJulToLog4j(java.util.logging.Level.SEVERE, Level.ERROR);
+        mapJulToLog4j(java.util.logging.Level.OFF, Level.OFF);
+        // Map Log4j to JUL
+        mapLog4jToJul(Level.ALL, java.util.logging.Level.ALL);
+        mapLog4jToJul(LevelTranslator.FINEST, java.util.logging.Level.FINEST);
+        mapLog4jToJul(Level.TRACE, java.util.logging.Level.FINER);
+        mapLog4jToJul(Level.DEBUG, java.util.logging.Level.FINE);
+        mapLog4jToJul(LevelTranslator.CONFIG, java.util.logging.Level.CONFIG);
+        mapLog4jToJul(Level.INFO, java.util.logging.Level.INFO);
+        mapLog4jToJul(Level.WARN, java.util.logging.Level.WARNING);
+        mapLog4jToJul(Level.ERROR, java.util.logging.Level.SEVERE);
+        mapLog4jToJul(Level.FATAL, java.util.logging.Level.SEVERE);
+        mapLog4jToJul(Level.OFF, java.util.logging.Level.OFF);
+        // Sorted Java levels
+        sortedJulLevels.addAll(julToLog4j.keySet());
+        Collections.sort(sortedJulLevels, new JulLevelComparator());
+    }
+
+    private long distance(final java.util.logging.Level javaLevel, final 
java.util.logging.Level customJavaLevel) {
+        return Math.abs((long) customJavaLevel.intValue() - (long) 
javaLevel.intValue());
+    }
+
+    /*
+     * TODO consider making public for advanced configuration.
+     */
+    private void mapJulToLog4j(final java.util.logging.Level julLevel, final 
Level level) {
+        julToLog4j.put(julLevel, level);
+    }
+
+    /*
+     * TODO consider making public for advanced configuration.
+     */
+    private void mapLog4jToJul(final Level level, final 
java.util.logging.Level julLevel) {
+        log4jToJul.put(level, julLevel);
+    }
+
+    private Level nearestLevel(final java.util.logging.Level customJavaLevel) {
+        long prevDist = Long.MAX_VALUE;
+        java.util.logging.Level prevLevel = null;
+        for (final java.util.logging.Level mappedJavaLevel : sortedJulLevels) {
+            final long distance = distance(customJavaLevel, mappedJavaLevel);
+            if (distance > prevDist) {
+                return julToLog4j.get(prevLevel);
+            }
+            prevDist = distance;
+            prevLevel = mappedJavaLevel;
+        }
+        return julToLog4j.get(prevLevel);
+    }
+
+    @Override
+    public java.util.logging.Level toJavaLevel(final Level level) {
+        return log4jToJul.get(level);
+    }
+
+    @Override
+    public Level toLevel(final java.util.logging.Level javaLevel) {
+        if (javaLevel == null) {
+            return null;
+        }
+        final Level level = julToLog4j.get(javaLevel);
+        if (level != null) {
+            return level;
+        }
+        final Level nearestLevel = nearestLevel(javaLevel);
+        julToLog4j.put(javaLevel, nearestLevel);
+        return nearestLevel;
+    }
+}
diff --git 
a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/JulProperties.java
 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/JulProperties.java
new file mode 100644
index 0000000..1b500b6
--- /dev/null
+++ 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/JulProperties.java
@@ -0,0 +1,27 @@
+/*
+ * 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.jul.tolog4j.internal;
+
+import org.apache.logging.jul.tolog4j.spi.AbstractLoggerAdapter;
+import org.apache.logging.jul.tolog4j.spi.LevelConverter;
+import org.apache.logging.log4j.kit.env.Log4jProperty;
+import org.jspecify.annotations.Nullable;
+
+@Log4jProperty(name = "jul")
+public record JulProperties(
+        @Nullable Class<? extends LevelConverter> levelConverter,
+        @Nullable Class<? extends AbstractLoggerAdapter> loggerAdapter) {}
diff --git 
a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/NoOpLogger.java
 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/NoOpLogger.java
new file mode 100644
index 0000000..879d19a
--- /dev/null
+++ 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/NoOpLogger.java
@@ -0,0 +1,222 @@
+/*
+ * 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.jul.tolog4j.internal;
+
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+/**
+ * Dummy version of a java.util.Logger.
+ */
+public class NoOpLogger extends Logger {
+
+    public NoOpLogger(final String name) {
+        super(name, null);
+    }
+
+    @Override
+    public void log(final LogRecord record) {}
+
+    @Override
+    public void log(final Level level, final String msg) {}
+
+    @Override
+    public void log(final Level level, final Supplier<String> msgSupplier) {}
+
+    @Override
+    public void log(final Level level, final String msg, final Object param1) 
{}
+
+    @Override
+    public void log(final Level level, final String msg, final Object[] 
params) {}
+
+    @Override
+    public void log(final Level level, final String msg, final Throwable 
thrown) {}
+
+    @Override
+    public void log(final Level level, final Throwable thrown, final 
Supplier<String> msgSupplier) {}
+
+    @Override
+    public void logp(final Level level, final String sourceClass, final String 
sourceMethod, final String msg) {}
+
+    @Override
+    public void logp(
+            final Level level,
+            final String sourceClass,
+            final String sourceMethod,
+            final Supplier<String> msgSupplier) {}
+
+    @Override
+    public void logp(
+            final Level level,
+            final String sourceClass,
+            final String sourceMethod,
+            final String msg,
+            final Object param1) {}
+
+    @Override
+    public void logp(
+            final Level level,
+            final String sourceClass,
+            final String sourceMethod,
+            final String msg,
+            final Object[] params) {}
+
+    @Override
+    public void logp(
+            final Level level,
+            final String sourceClass,
+            final String sourceMethod,
+            final String msg,
+            final Throwable thrown) {}
+
+    @Override
+    public void logp(
+            final Level level,
+            final String sourceClass,
+            final String sourceMethod,
+            final Throwable thrown,
+            final Supplier<String> msgSupplier) {}
+
+    @Override
+    public void logrb(
+            final Level level,
+            final String sourceClass,
+            final String sourceMethod,
+            final String bundleName,
+            final String msg) {}
+
+    @Override
+    public void logrb(
+            final Level level,
+            final String sourceClass,
+            final String sourceMethod,
+            final String bundleName,
+            final String msg,
+            final Object param1) {}
+
+    @Override
+    public void logrb(
+            final Level level,
+            final String sourceClass,
+            final String sourceMethod,
+            final String bundleName,
+            final String msg,
+            final Object[] params) {}
+
+    @Override
+    public void logrb(
+            final Level level,
+            final String sourceClass,
+            final String sourceMethod,
+            final ResourceBundle bundle,
+            final String msg,
+            final Object... params) {}
+
+    @Override
+    public void logrb(
+            final Level level,
+            final String sourceClass,
+            final String sourceMethod,
+            final String bundleName,
+            final String msg,
+            final Throwable thrown) {}
+
+    @Override
+    public void logrb(
+            final Level level,
+            final String sourceClass,
+            final String sourceMethod,
+            final ResourceBundle bundle,
+            final String msg,
+            final Throwable thrown) {}
+
+    @Override
+    public void entering(final String sourceClass, final String sourceMethod) 
{}
+
+    @Override
+    public void entering(final String sourceClass, final String sourceMethod, 
final Object param1) {}
+
+    @Override
+    public void entering(final String sourceClass, final String sourceMethod, 
final Object[] params) {}
+
+    @Override
+    public void exiting(final String sourceClass, final String sourceMethod) {}
+
+    @Override
+    public void exiting(final String sourceClass, final String sourceMethod, 
final Object result) {}
+
+    @Override
+    public void throwing(final String sourceClass, final String sourceMethod, 
final Throwable thrown) {}
+
+    @Override
+    public void severe(final String msg) {}
+
+    @Override
+    public void warning(final String msg) {}
+
+    @Override
+    public void info(final String msg) {}
+
+    @Override
+    public void config(final String msg) {}
+
+    @Override
+    public void fine(final String msg) {}
+
+    @Override
+    public void finer(final String msg) {}
+
+    @Override
+    public void finest(final String msg) {}
+
+    @Override
+    public void severe(final Supplier<String> msgSupplier) {}
+
+    @Override
+    public void warning(final Supplier<String> msgSupplier) {}
+
+    @Override
+    public void info(final Supplier<String> msgSupplier) {}
+
+    @Override
+    public void config(final Supplier<String> msgSupplier) {}
+
+    @Override
+    public void fine(final Supplier<String> msgSupplier) {}
+
+    @Override
+    public void finer(final Supplier<String> msgSupplier) {}
+
+    @Override
+    public void finest(final Supplier<String> msgSupplier) {}
+
+    @Override
+    public void setLevel(final Level newLevel) throws SecurityException {}
+
+    @Override
+    public Level getLevel() {
+        return Level.OFF;
+    }
+
+    @Override
+    public boolean isLoggable(final Level level) {
+        return false;
+    }
+}
diff --git 
a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/package-info.java 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/package-info.java
new file mode 100644
index 0000000..d0fc9b2
--- /dev/null
+++ 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+@Export
+@Version("3.0.0")
+package org.apache.logging.jul.tolog4j;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
diff --git 
a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/AbstractLoggerAdapter.java
 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/AbstractLoggerAdapter.java
new file mode 100644
index 0000000..8c43dc8
--- /dev/null
+++ 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/AbstractLoggerAdapter.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.jul.tolog4j.spi;
+
+import java.util.logging.Logger;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.spi.LoggerContext;
+import org.apache.logging.log4j.util.StackLocatorUtil;
+
+/**
+ * Abstract Logger registry.
+ * <p>
+ *     JUL contains methods, such as {@link Logger#setLevel}, which modify the 
configuration of the logging backend.
+ *     To fully implement all {@code Logger} methods, we need to provide a 
different {@code Logger} implementation
+ *     for each Log4j API implementation.
+ * </p>
+ * <p>
+ *     Older Log4j versions provided an alternative {@code CoreLoggerAdapter} 
implementation that supported
+ *     the modification of Log4j Core configuration using JUL.
+ * </p>
+ *     Since version 2.24.0, however, this implementation was deprecated for 
removal.
+ *     If you wish to enable this feature again, you need to implement this 
class and provide its FQCN
+ *     as {@code log4j.jul.loggerAdapter} configuration property.
+ * </p>
+ * <p>
+ *     <strong>Implementation note:</strong> since version 3.0.0, this 
interface was moved to a new package.
+ * </p>
+ *
+ * @see <a href="https://github.com/apache/logging-log4j2/issues/2353";>Issue 
#2353</a>
+ * @since 2.1
+ */
+public abstract class AbstractLoggerAdapter extends 
org.apache.logging.log4j.spi.AbstractLoggerAdapter<Logger> {
+
+    /**
+     * Creates a new {@link java.util.logging.Logger}
+     * <p>
+     *     Each implementation should provide this method.
+     * </p>
+     */
+    @Override
+    public abstract Logger newLogger(String name, LoggerContext context);
+
+    /**
+     * Provides the most appropriate {@link LoggerContext} for the caller.
+     */
+    @Override
+    public LoggerContext getContext() {
+        return getContext(
+                LogManager.getFactory().isClassLoaderDependent()
+                        ? 
StackLocatorUtil.getCallerClass(java.util.logging.LogManager.class)
+                        : null);
+    }
+}
diff --git 
a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/LevelConverter.java
 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/LevelConverter.java
new file mode 100644
index 0000000..489477e
--- /dev/null
+++ 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/LevelConverter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.jul.tolog4j.spi;
+
+import org.apache.logging.jul.tolog4j.internal.JulProperties;
+import org.apache.logging.log4j.Level;
+
+/**
+ * Strategy interface to convert between custom Log4j {@link Level Levels} and 
JUL
+ * {@link java.util.logging.Level Levels}.
+ * <p>
+ *     <strong>Implementation note:</strong> since version 3.0.0, this 
interface was moved to a new package.
+ * </p>
+ *
+ * @see JulProperties#levelConverter()
+ * @since 2.1
+ */
+public interface LevelConverter {
+
+    /**
+     * Converts a JDK logging Level to a Log4j logging Level.
+     *
+     * @param javaLevel JDK Level to convert, may be null per the JUL 
specification.
+     * @return converted Level or {@code null} if the given level could not be 
converted.
+     */
+    Level toLevel(java.util.logging.Level javaLevel);
+
+    /**
+     * Converts a Log4j logging Level to a JDK logging Level.
+     *
+     * @param level Log4j Level to convert.
+     * @return converted Level or {@code null} if the given level could not be 
converted.
+     */
+    java.util.logging.Level toJavaLevel(Level level);
+}
diff --git 
a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/package-info.java
 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/package-info.java
new file mode 100644
index 0000000..b7eb30c
--- /dev/null
+++ 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+/**
+ * Contains interfaces an abstract classes to extend the functionality of 
Log4j JUL Adapter.
+ */
+@Export
+@Version("3.0.0")
+package org.apache.logging.jul.tolog4j.spi;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
diff --git 
a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/support/AbstractLogger.java
 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/support/AbstractLogger.java
new file mode 100644
index 0000000..f91812b
--- /dev/null
+++ 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/support/AbstractLogger.java
@@ -0,0 +1,441 @@
+/*
+ * 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.jul.tolog4j.support;
+
+import static org.apache.logging.log4j.spi.AbstractLogger.ENTRY_MARKER;
+import static org.apache.logging.log4j.spi.AbstractLogger.EXIT_MARKER;
+import static org.apache.logging.log4j.spi.AbstractLogger.THROWING_MARKER;
+
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
+import java.util.logging.Filter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import org.apache.logging.jul.tolog4j.LevelTranslator;
+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.ExtendedLogger;
+
+/**
+ * Log4j API implementation of the JUL {@link Logger} class.
+ * <p>
+ *     <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.Logger}
+ *     which may be implemented in one of many different ways.
+ *     Consult the documentation for your Log4j API Provider for more details.
+ * </p>
+ * <p>
+ *     Note that the methods {@link #getParent()} and mutator methods such as 
{@link #setLevel(java.util.logging.Level)}
+ *     must be provided by implementations of this class.
+ *     The default {@link org.apache.logging.jul.tolog4j.internal.ApiLogger} 
implementations just ignores them.
+ *     If you need support for these methods, then you'll need to provide your 
own
+ *     {@link org.apache.logging.jul.tolog4j.spi.AbstractLoggerAdapter}.
+ * </p>
+ *
+ * @since 3.0.0
+ */
+public abstract class AbstractLogger extends Logger {
+
+    private final ExtendedLogger logger;
+    private static final String FQCN = AbstractLogger.class.getName();
+
+    protected AbstractLogger(final ExtendedLogger logger) {
+        super(logger.getName(), null);
+        final Level javaLevel = LevelTranslator.toJavaLevel(logger.getLevel());
+        super.setLevel(javaLevel);
+        this.logger = logger;
+    }
+
+    @Override
+    public void log(final LogRecord record) {
+        final org.apache.logging.log4j.Level level = 
LevelTranslator.toLevel(record.getLevel());
+        final Object[] parameters = record.getParameters();
+        final MessageFactory messageFactory = logger.getMessageFactory();
+        final Message message = parameters == null
+                ? messageFactory.newMessage(record.getMessage()) /* 
LOG4J2-1251: not formatted case */
+                : messageFactory.newMessage(record.getMessage(), parameters);
+        final Throwable thrown = record.getThrown();
+        logger.logIfEnabled(FQCN, level, null, message, thrown);
+    }
+
+    // <editor-fold desc="Configuration methods">
+    // Methods
+
+    @Override
+    public abstract void setFilter(Filter newFilter) throws SecurityException;
+
+    @Override
+    public abstract void setLevel(Level newLevel) throws SecurityException;
+
+    @Override
+    public abstract void addHandler(Handler handler) throws SecurityException;
+
+    @Override
+    public abstract void removeHandler(Handler handler) throws 
SecurityException;
+
+    @Override
+    public abstract void setUseParentHandlers(boolean useParentHandlers);
+
+    @Override
+    public abstract void setParent(Logger parent);
+
+    @Override
+    public Filter getFilter() {
+        return null;
+    }
+
+    /**
+     * Returns the configured level of a logger.
+     * <p>
+     *     <strong>Implementation note:</strong> this method returns the level 
<strong>explicitly</strong> configured
+     *     in the Log4j API logging implementation and is implementation 
specific.
+     *     The default implementation always returns {@code null}.
+     * </p>
+     * <p>
+     *     To test if a logger is enabled for a specific logging level, i.e. 
to test its <strong>effective</strong>
+     *     level, use {@link Logger#isLoggable(Level)}.
+     * </p>
+     * @see #isLoggable(Level)
+     */
+    @Override
+    public Level getLevel() {
+        return null;
+    }
+
+    @Override
+    public Handler[] getHandlers() {
+        return new Handler[0];
+    }
+
+    @Override
+    public boolean getUseParentHandlers() {
+        return false;
+    }
+
+    @Override
+    public Logger getParent() {
+        return null;
+    }
+
+    // </editor-fold>
+
+    // <editor-fold desc="Logging methods">
+    // Implementation of methods used for logging
+
+    @Override
+    public boolean isLoggable(final Level level) {
+        return logger.isEnabled(LevelTranslator.toLevel(level));
+    }
+
+    @Override
+    public String getName() {
+        return logger.getName();
+    }
+
+    private org.apache.logging.log4j.util.Supplier<String> 
toLog4jSupplier(Supplier<String> msgSupplier) {
+        return msgSupplier::get;
+    }
+
+    private org.apache.logging.log4j.util.Supplier<Message> 
toMessageSupplier(Supplier<String> msgSupplier) {
+        return () -> logger.getMessageFactory().newMessage(msgSupplier.get());
+    }
+
+    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<Message> toMessageSupplier(
+            ResourceBundle bundle, String msg, Object[] params) {
+        return () -> new LocalizedMessage(bundle, msg, params);
+    }
+
+    private StackTraceElement toLocation(String sourceClass, String 
sourceMethod) {
+        return new StackTraceElement(sourceClass, sourceMethod, null, 0);
+    }
+
+    @Override
+    public void log(final Level level, final String msg) {
+        logger.logIfEnabled(FQCN, LevelTranslator.toLevel(level), null, msg);
+    }
+
+    /**
+     * @since 3.0.0
+     */
+    @Override
+    public void log(Level level, Supplier<String> msgSupplier) {
+        logger.logIfEnabled(FQCN, LevelTranslator.toLevel(level), null, 
toLog4jSupplier(msgSupplier), null);
+    }
+
+    @Override
+    public void log(final Level level, final String msg, final Object param1) {
+        logger.logIfEnabled(FQCN, LevelTranslator.toLevel(level), null, msg, 
param1);
+    }
+
+    @Override
+    public void log(final Level level, final String msg, final Object[] 
params) {
+        logger.logIfEnabled(FQCN, LevelTranslator.toLevel(level), null, msg, 
params);
+    }
+
+    @Override
+    public void log(final Level level, final String msg, final Throwable 
thrown) {
+        logger.logIfEnabled(FQCN, LevelTranslator.toLevel(level), null, msg, 
thrown);
+    }
+
+    /**
+     * @since 3.0.0
+     */
+    @Override
+    public void log(Level level, Throwable thrown, Supplier<String> 
msgSupplier) {
+        logger.logIfEnabled(FQCN, LevelTranslator.toLevel(level), null, 
toLog4jSupplier(msgSupplier), thrown);
+    }
+
+    @Override
+    public void logp(final Level level, final String sourceClass, final String 
sourceMethod, final String msg) {
+        logger.atLevel(LevelTranslator.toLevel(level))
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .log(msg);
+    }
+
+    /**
+     * @since 3.0.0
+     */
+    @Override
+    public void logp(Level level, String sourceClass, String sourceMethod, 
Supplier<String> msgSupplier) {
+        logger.atLevel(LevelTranslator.toLevel(level))
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .log(toMessageSupplier(msgSupplier));
+    }
+
+    @Override
+    public void logp(
+            final Level level,
+            final String sourceClass,
+            final String sourceMethod,
+            final String msg,
+            final Object param1) {
+        logger.atLevel(LevelTranslator.toLevel(level))
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .log(msg, param1);
+    }
+
+    @Override
+    public void logp(
+            final Level level,
+            final String sourceClass,
+            final String sourceMethod,
+            final String msg,
+            final Object[] params) {
+        logger.atLevel(LevelTranslator.toLevel(level))
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .log(msg, params);
+    }
+
+    @Override
+    public void logp(
+            final Level level,
+            final String sourceClass,
+            final String sourceMethod,
+            final String msg,
+            final Throwable thrown) {
+        logger.atLevel(LevelTranslator.toLevel(level))
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withThrowable(thrown)
+                .log(msg);
+    }
+
+    /**
+     * @since 3.0.0
+     */
+    @Override
+    public void logp(
+            Level level, String sourceClass, String sourceMethod, Throwable 
thrown, Supplier<String> msgSupplier) {
+        logger.atLevel(LevelTranslator.toLevel(level))
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withThrowable(thrown)
+                .log(toMessageSupplier(msgSupplier));
+    }
+
+    /**
+     * @since 3.0.0
+     */
+    @Override
+    public void logrb(
+            Level level, String sourceClass, String sourceMethod, 
ResourceBundle bundle, String msg, Object... params) {
+        logger.atLevel(LevelTranslator.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) {
+        logger.atLevel(LevelTranslator.toLevel(level))
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withThrowable(thrown)
+                .log(toMessageSupplier(bundle, msg));
+    }
+
+    /**
+     * @since 3.0.0
+     */
+    @Override
+    public void logrb(Level level, ResourceBundle bundle, String msg, 
Object... params) {
+        logger.logIfEnabled(FQCN, LevelTranslator.toLevel(level), null, 
toMessageSupplier(bundle, msg, params), null);
+    }
+
+    /**
+     * @since 3.0.0
+     */
+    @Override
+    public void logrb(Level level, ResourceBundle bundle, String msg, 
Throwable thrown) {
+        LogBuilder builder = 
logger.atLevel(LevelTranslator.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.atTrace()
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withMarker(ENTRY_MARKER)
+                .log(DefaultFlowMessageFactory.INSTANCE.newEntryMessage(null, 
(Object[]) null));
+    }
+
+    @Override
+    public void entering(final String sourceClass, final String sourceMethod, 
final Object param1) {
+        logger.atTrace()
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withMarker(ENTRY_MARKER)
+                .log(DefaultFlowMessageFactory.INSTANCE.newEntryMessage(null, 
param1));
+    }
+
+    @Override
+    public void entering(final String sourceClass, final String sourceMethod, 
final Object[] params) {
+        logger.atTrace()
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withMarker(ENTRY_MARKER)
+                .log(DefaultFlowMessageFactory.INSTANCE.newEntryMessage(null, 
params));
+    }
+
+    @Override
+    public void exiting(final String sourceClass, final String sourceMethod) {
+        logger.atTrace()
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withMarker(EXIT_MARKER)
+                .log(DefaultFlowMessageFactory.INSTANCE.newExitMessage(null, 
(Object) null));
+    }
+
+    @Override
+    public void exiting(final String sourceClass, final String sourceMethod, 
final Object result) {
+        logger.atTrace()
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withMarker(EXIT_MARKER)
+                .log(DefaultFlowMessageFactory.INSTANCE.newExitMessage(null, 
result));
+    }
+
+    @Override
+    public void throwing(final String sourceClass, final String sourceMethod, 
final Throwable thrown) {
+        logger.atTrace()
+                .withLocation(toLocation(sourceClass, sourceMethod))
+                .withMarker(THROWING_MARKER)
+                .withThrowable(thrown)
+                .log("Throwing");
+    }
+
+    @Override
+    public void severe(final String msg) {
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.ERROR, null, 
msg);
+    }
+
+    /**
+     * @since 3.0.0
+     */
+    @Override
+    public void severe(Supplier<String> msgSupplier) {
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.ERROR, null, 
toLog4jSupplier(msgSupplier), null);
+    }
+
+    @Override
+    public void warning(final String msg) {
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.WARN, null, 
msg);
+    }
+
+    @Override
+    public void warning(Supplier<String> msgSupplier) {
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.WARN, null, 
toLog4jSupplier(msgSupplier), null);
+    }
+
+    @Override
+    public void info(final String msg) {
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.INFO, null, 
msg);
+    }
+
+    @Override
+    public void info(Supplier<String> msgSupplier) {
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.INFO, null, 
toLog4jSupplier(msgSupplier), null);
+    }
+
+    @Override
+    public void config(final String msg) {
+        logger.logIfEnabled(FQCN, LevelTranslator.CONFIG, null, msg);
+    }
+
+    @Override
+    public void config(Supplier<String> msgSupplier) {
+        logger.logIfEnabled(FQCN, LevelTranslator.CONFIG, null, 
toLog4jSupplier(msgSupplier), null);
+    }
+
+    @Override
+    public void fine(final String msg) {
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.DEBUG, null, 
msg);
+    }
+
+    @Override
+    public void fine(Supplier<String> msgSupplier) {
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.DEBUG, null, 
toLog4jSupplier(msgSupplier), null);
+    }
+
+    @Override
+    public void finer(final String msg) {
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.TRACE, null, 
msg);
+    }
+
+    @Override
+    public void finer(Supplier<String> msgSupplier) {
+        logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.TRACE, null, 
toLog4jSupplier(msgSupplier), null);
+    }
+
+    @Override
+    public void finest(final String msg) {
+        logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, msg);
+    }
+
+    @Override
+    public void finest(Supplier<String> msgSupplier) {
+        logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, 
toLog4jSupplier(msgSupplier), null);
+    }
+    // </editor-fold>
+}
diff --git 
a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/support/package-info.java
 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/support/package-info.java
new file mode 100644
index 0000000..8a9b130
--- /dev/null
+++ 
b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/support/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+/**
+ * Utility classes that can be used in implementing providing implementation 
of the classes in
+ * {@link org.apache.logging.log4j.jul.spi}.
+ */
+@Export
+@Version("3.0.0")
+package org.apache.logging.jul.tolog4j.support;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
diff --git 
a/jul-to-log4j/src/main/resources/META-INF/log4j/propertyMapping.json 
b/jul-to-log4j/src/main/resources/META-INF/log4j/propertyMapping.json
new file mode 100644
index 0000000..f88a328
--- /dev/null
+++ b/jul-to-log4j/src/main/resources/META-INF/log4j/propertyMapping.json
@@ -0,0 +1,10 @@
+{
+  "jul": {
+    "levelConverter": [
+      "log4j2.julLevelConverter"
+    ],
+    "loggerAdapter": [
+      "log4j2.julLoggerAdapter"
+    ]
+  }
+}
\ No newline at end of file
diff --git 
a/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/AsyncLoggerThreadsTest.java
 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/AsyncLoggerThreadsTest.java
new file mode 100644
index 0000000..1ec5eb6
--- /dev/null
+++ 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/AsyncLoggerThreadsTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.jul.tolog4j.test;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.async.logger.AsyncLoggerContextSelector;
+import org.apache.logging.log4j.core.test.TestConstants;
+import org.apache.logging.log4j.core.test.categories.AsyncLoggers;
+import org.jspecify.annotations.Nullable;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category(AsyncLoggers.class)
+public class AsyncLoggerThreadsTest {
+
+    static @Nullable String oldSelector;
+
+    @BeforeClass
+    public static void beforeClass() {
+        oldSelector = TestConstants.setSystemProperty(
+                TestConstants.LOGGER_CONTEXT_SELECTOR, 
AsyncLoggerContextSelector.class.getName());
+        System.setProperty("java.util.logging.manager", 
LogManager.class.getName());
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        TestConstants.setSystemProperty(TestConstants.LOGGER_CONTEXT_SELECTOR, 
oldSelector);
+        System.clearProperty("java.util.logging.manager");
+    }
+
+    @Test
+    public void testAsyncLoggerThreads() {
+        LogManager.getLogger("com.foo.Bar").info("log");
+        final List<Thread> asyncLoggerThreads = 
Thread.getAllStackTraces().keySet().stream()
+                .filter(thread -> 
thread.getName().matches("Log4j2-TF.*AsyncLogger.*"))
+                .collect(Collectors.toList());
+        assertEquals(asyncLoggerThreads.toString(), 1, 
asyncLoggerThreads.size());
+    }
+}
diff --git 
a/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/BracketInNotInterpolatedMessageTest.java
 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/BracketInNotInterpolatedMessageTest.java
new file mode 100644
index 0000000..3bb6fd0
--- /dev/null
+++ 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/BracketInNotInterpolatedMessageTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.jul.tolog4j.test;
+
+import static java.util.logging.Level.INFO;
+import static org.hamcrest.Matchers.hasSize;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.util.List;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import org.apache.logging.jul.tolog4j.LogManager;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.test.appender.ListAppender;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class BracketInNotInterpolatedMessageTest {
+
+    @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");
+    }
+
+    @Test
+    public void noInterpolation() {
+        final Logger logger = Logger.getLogger("Test");
+        logger.info("{raw}");
+        logger.log(
+                new LogRecord(INFO, "{raw}")); // should lead to the same as 
previous but was not the case LOG4J2-1251
+        final List<LogEvent> events =
+                ListAppender.getListAppender("TestAppender").getEvents();
+        assertThat(events, hasSize(2));
+        assertEquals("{raw}", 
events.get(0).getMessage().getFormattedMessage());
+        assertEquals("{raw}", 
events.get(1).getMessage().getFormattedMessage());
+    }
+}
diff --git 
a/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/CallerInformationTest.java
 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/CallerInformationTest.java
new file mode 100644
index 0000000..1562b70
--- /dev/null
+++ 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/CallerInformationTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.jul.tolog4j.test;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.logging.jul.tolog4j.LogManager;
+import org.apache.logging.log4j.core.test.appender.ListAppender;
+import org.apache.logging.log4j.core.test.junit.LoggerContextRule;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class CallerInformationTest {
+
+    private static final String PARAM_1 = "PARAM_1";
+    private static final String[] PARAMS = {PARAM_1, "PARAM_2"};
+    private static final String SOURCE_CLASS = "SourceClass";
+    private static final String SOURCE_METHOD = "sourceMethod";
+
+    @Rule
+    public final LoggerContextRule ctx = new 
LoggerContextRule("CallerInformationTest.xml");
+
+    @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");
+    }
+
+    @Test
+    public void testClassLogger() {
+        final ListAppender app = ctx.getListAppender("Class").clear();
+        final Logger logger = Logger.getLogger("ClassLogger");
+        // 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.", 19, messages.size());
+        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
+        app.clear();
+        logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello!");
+        logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello {1}!", 
PARAM_1);
+        logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello {1} and 
{2}!", PARAMS);
+        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.", 12, messages.size());
+        for (int i = 0; i < messages.size(); i++) {
+            String message = messages.get(i);
+            assertEquals("Incorrect caller class name for message " + i, 
SOURCE_CLASS, message);
+        }
+    }
+
+    @Test
+    public void testMethodLogger() {
+        final ListAppender app = ctx.getListAppender("Method").clear();
+        final Logger logger = Logger.getLogger("MethodLogger");
+        // 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.", 19, messages.size());
+        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
+        app.clear();
+        logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello!");
+        logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello {1}!", 
PARAM_1);
+        logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello {1} and 
{2}!", PARAMS);
+        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.", 12, messages.size());
+        for (int i = 0; i < messages.size(); i++) {
+            String message = messages.get(i);
+            assertEquals("Incorrect caller class name for message " + i, 
SOURCE_METHOD, message);
+        }
+    }
+}
diff --git 
a/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/JavaLevelTranslatorTest.java
 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/JavaLevelTranslatorTest.java
new file mode 100644
index 0000000..76744a3
--- /dev/null
+++ 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/JavaLevelTranslatorTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.jul.tolog4j.test;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collection;
+import org.apache.logging.jul.tolog4j.LevelTranslator;
+import org.apache.logging.log4j.Level;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Tests that all JUL levels are mapped to a Log4j level.
+ */
+@RunWith(Parameterized.class)
+public class JavaLevelTranslatorTest {
+
+    private final java.util.logging.Level javaLevel;
+    private final Level log4jLevel;
+
+    public JavaLevelTranslatorTest(final java.util.logging.Level javaLevel, 
final Level log4jLevel) {
+        this.javaLevel = javaLevel;
+        this.log4jLevel = log4jLevel;
+    }
+
+    @Parameterized.Parameters
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] {
+            // All 9 JUL levels, All 8 Log4j levels and extras
+            // @formatter:off
+            {java.util.logging.Level.OFF, Level.OFF},
+            {java.util.logging.Level.SEVERE, Level.ERROR},
+            {java.util.logging.Level.WARNING, Level.WARN},
+            {java.util.logging.Level.INFO, Level.INFO},
+            {java.util.logging.Level.CONFIG, LevelTranslator.CONFIG},
+            {java.util.logging.Level.FINE, Level.DEBUG},
+            {java.util.logging.Level.FINER, Level.TRACE},
+            {java.util.logging.Level.FINEST, LevelTranslator.FINEST},
+            {java.util.logging.Level.ALL, Level.ALL}
+            // @formatter:on
+        });
+    }
+
+    @Test
+    public void testToLevel() throws Exception {
+        final Level actualLevel = LevelTranslator.toLevel(javaLevel);
+        assertEquals(log4jLevel, actualLevel);
+    }
+
+    @Test
+    public void testToJavaLevel() throws Exception {
+        final java.util.logging.Level actualLevel = 
LevelTranslator.toJavaLevel(log4jLevel);
+        assertEquals(javaLevel, actualLevel);
+    }
+}
diff --git 
a/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/JulTestProperties.java
 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/JulTestProperties.java
new file mode 100644
index 0000000..28c4b2f
--- /dev/null
+++ 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/JulTestProperties.java
@@ -0,0 +1,24 @@
+/*
+ * 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.jul.tolog4j.test;
+
+public final class JulTestProperties {
+
+    public static final String JUL_LOGGER_ADAPTER = "log4j.jul.loggerAdapter";
+
+    private JulTestProperties() {}
+}
diff --git 
a/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/Log4jLevelTranslatorTest.java
 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/Log4jLevelTranslatorTest.java
new file mode 100644
index 0000000..43b6796
--- /dev/null
+++ 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/Log4jLevelTranslatorTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.jul.tolog4j.test;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collection;
+import org.apache.logging.jul.tolog4j.LevelTranslator;
+import org.apache.logging.log4j.Level;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Tests that all Log4j levels are mapped to a JUL level.
+ */
+@RunWith(Parameterized.class)
+public class Log4jLevelTranslatorTest {
+
+    private final java.util.logging.Level javaLevel;
+    private final Level log4jLevel;
+
+    public Log4jLevelTranslatorTest(final java.util.logging.Level javaLevel, 
final Level log4jLevel) {
+        this.javaLevel = javaLevel;
+        this.log4jLevel = log4jLevel;
+    }
+
+    @Parameterized.Parameters
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] {
+            // Some JUL levels, All 8 Log4j levels
+            // @formatter:off
+            {java.util.logging.Level.OFF, Level.OFF},
+            {java.util.logging.Level.SEVERE, Level.FATAL},
+            {java.util.logging.Level.SEVERE, Level.ERROR},
+            {java.util.logging.Level.WARNING, Level.WARN},
+            {java.util.logging.Level.INFO, Level.INFO},
+            {java.util.logging.Level.FINE, Level.DEBUG},
+            {java.util.logging.Level.FINER, Level.TRACE},
+            {java.util.logging.Level.ALL, Level.ALL},
+            // @formatter:on
+        });
+    }
+
+    @Test
+    public void testToJavaLevel() throws Exception {
+        final java.util.logging.Level actualLevel = 
LevelTranslator.toJavaLevel(log4jLevel);
+        assertEquals(javaLevel, actualLevel);
+    }
+}
diff --git 
a/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/ResourceBundleTest.java
 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/ResourceBundleTest.java
new file mode 100644
index 0000000..ed94daa
--- /dev/null
+++ 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/ResourceBundleTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.jul.tolog4j.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import java.util.ResourceBundle;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.logging.jul.tolog4j.LogManager;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.test.appender.ListAppender;
+import org.apache.logging.log4j.core.test.junit.LoggerContextRule;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test methods that accept a resource bundle
+ */
+public class ResourceBundleTest {
+
+    private static final ResourceBundle BUNDLE = 
ResourceBundle.getBundle("ResourceBundleTest");
+    private static final String SOURCE_CLASS = "SourceClass";
+    private static final String SOURCE_METHOD = "sourceMethod";
+
+    private static final String[] EXPECTED_MESSAGES = {"Hello!", "Hello Log4j 
and JUL!"};
+    private static final String[] EXPECTED_CLASS_NAMES = 
{ResourceBundleTest.class.getName(), SOURCE_CLASS};
+    private static final String[] EXPECTED_METHOD_NAMES = 
{"testCorrectMessageAndLocation", SOURCE_METHOD};
+
+    @Rule
+    public final LoggerContextRule ctx = new 
LoggerContextRule("ResourceBundleTest.xml");
+
+    @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");
+    }
+
+    @Test
+    public void testCorrectMessageAndLocation() {
+        ListAppender appender = ctx.getListAppender("LIST").clear();
+        Logger logger = Logger.getLogger(ResourceBundleTest.class.getName());
+
+        Throwable thrown = new RuntimeException();
+        logger.logrb(Level.INFO, BUNDLE, "msg_1", thrown);
+        logger.logrb(Level.INFO, BUNDLE, "msg_2", "Log4j", "JUL");
+        logger.logrb(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, BUNDLE, "msg_1", 
thrown);
+        logger.logrb(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, BUNDLE, "msg_2", 
"Log4j", "JUL");
+
+        LogEvent[] logEvents = appender.getEvents().toArray(LogEvent[]::new);
+        for (int idx = 0; idx < logEvents.length; ++idx) {
+            assertEquals(
+                    String.format("Message of event %d", idx),
+                    EXPECTED_MESSAGES[idx % 2],
+                    logEvents[idx].getMessage().getFormattedMessage());
+            assertEquals(
+                    String.format("Source class of event %d", idx),
+                    EXPECTED_CLASS_NAMES[idx / 2],
+                    logEvents[idx].getSource().getClassName());
+            assertEquals(
+                    String.format("Source method of event %d", idx),
+                    EXPECTED_METHOD_NAMES[idx / 2],
+                    logEvents[idx].getSource().getMethodName());
+            assertSame(
+                    String.format("Exception of event %d", idx),
+                    idx % 2 == 0 ? thrown : null,
+                    logEvents[idx].getThrown());
+        }
+    }
+}
diff --git 
a/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/ApiLoggerTest.java
 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/ApiLoggerTest.java
new file mode 100644
index 0000000..b7da811
--- /dev/null
+++ 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/ApiLoggerTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.jul.tolog4j.test.internal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.util.logging.Logger;
+import org.apache.logging.jul.tolog4j.LogManager;
+import org.apache.logging.jul.tolog4j.test.support.AbstractLoggerTest;
+import org.apache.logging.log4j.core.test.appender.ListAppender;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ApiLoggerTest 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() {
+        logger = Logger.getLogger(LOGGER_NAME);
+        logger.setFilter(null);
+        assertThat(logger.isLoggable(java.util.logging.Level.FINE))
+                .as("Level %s is enabled", java.util.logging.Level.FINE)
+                .isTrue();
+        assertThat(logger.isLoggable(java.util.logging.Level.FINER))
+                .as("Level %s is enabled", java.util.logging.Level.FINER)
+                .isFalse();
+        eventAppender = ListAppender.getListAppender("TestAppender");
+        flowAppender = ListAppender.getListAppender("FlowAppender");
+        stringAppender = ListAppender.getListAppender("StringAppender");
+        assertNotNull(eventAppender);
+        assertNotNull(flowAppender);
+        assertNotNull(stringAppender);
+    }
+
+    @After
+    public void tearDown() {
+        if (eventAppender != null) {
+            eventAppender.clear();
+        }
+        if (flowAppender != null) {
+            flowAppender.clear();
+        }
+        if (stringAppender != null) {
+            stringAppender.clear();
+        }
+    }
+
+    @Test
+    public void testGetParent() {
+        final Logger parent = logger.getParent();
+        assertNull("No parent logger should be automatically set up using 
log4j-api", parent);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testSetParentFails() {
+        logger.setParent(null);
+    }
+
+    @Test
+    public void testSetLevelFails() {
+        logger.setLevel(null);
+    }
+}
diff --git 
a/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/DefaultLevelConverterCustomJulLevelsTest.java
 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/DefaultLevelConverterCustomJulLevelsTest.java
new file mode 100644
index 0000000..825b476
--- /dev/null
+++ 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/DefaultLevelConverterCustomJulLevelsTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.jul.tolog4j.test.internal;
+
+import org.apache.logging.jul.tolog4j.LevelTranslator;
+import org.apache.logging.jul.tolog4j.internal.DefaultLevelConverter;
+import org.apache.logging.log4j.Level;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests {@link DefaultLevelConverter} for custom JUL levels.
+ *
+ * @since 2.4
+ */
+public class DefaultLevelConverterCustomJulLevelsTest {
+
+    static class CustomLevel extends java.util.logging.Level {
+
+        static CustomLevel ALL_P_1 = new CustomLevel("ALL_P_1", 
java.util.logging.Level.ALL.intValue() + 1);
+
+        static CustomLevel FINEST_P_1 = new CustomLevel("FINEST_P_1", 
java.util.logging.Level.FINEST.intValue() + 1);
+        static CustomLevel FINEST_M_1 = new CustomLevel("FINEST_M_1", 
java.util.logging.Level.FINEST.intValue() - 1);
+
+        static CustomLevel FINER_P_1 = new CustomLevel("FINER_P_1", 
java.util.logging.Level.FINER.intValue() + 1);
+        static CustomLevel FINER_M_1 = new CustomLevel("FINER_M_1", 
java.util.logging.Level.FINER.intValue() - 1);
+
+        static CustomLevel FINE_P_1 = new CustomLevel("FINE_P_1", 
java.util.logging.Level.FINE.intValue() + 1);
+        static CustomLevel FINE_M_1 = new CustomLevel("FINE_M_1", 
java.util.logging.Level.FINE.intValue() - 1);
+
+        static CustomLevel CONFIG_P_1 = new CustomLevel("CONFIG_P_1", 
java.util.logging.Level.CONFIG.intValue() + 1);
+        static CustomLevel CONFIG_M_1 = new CustomLevel("CONFIG_M_1", 
java.util.logging.Level.CONFIG.intValue() - 1);
+
+        static CustomLevel INFO_P_1 = new CustomLevel("INFO_P_1", 
java.util.logging.Level.INFO.intValue() + 1);
+        static CustomLevel INFO_M_1 = new CustomLevel("INFO_M_1", 
java.util.logging.Level.INFO.intValue() - 1);
+
+        static CustomLevel WARNING_P_1 = new CustomLevel("WARNING_P_1", 
java.util.logging.Level.WARNING.intValue() + 1);
+        static CustomLevel WARNING_M_1 = new CustomLevel("WARNING_M_1", 
java.util.logging.Level.WARNING.intValue() - 1);
+
+        static CustomLevel SEVERE_P_1 = new CustomLevel("SEVERE_P_1", 
java.util.logging.Level.SEVERE.intValue() + 1);
+        static CustomLevel SEVERE_M_1 = new CustomLevel("SEVERE_M_1", 
java.util.logging.Level.SEVERE.intValue() - 1);
+
+        static CustomLevel OFF_M_1 = new CustomLevel("OFF_M_1", 
java.util.logging.Level.OFF.intValue() - 1);
+
+        protected CustomLevel(final String name, final int value) {
+            super(name, value);
+        }
+    }
+
+    private final DefaultLevelConverter converter = new 
DefaultLevelConverter();
+
+    @Test
+    public void testCustomJulLevelNearAll() {
+        // Sanity check:
+        Assert.assertEquals(Level.ALL, 
converter.toLevel(java.util.logging.Level.ALL));
+        // Test:
+        Assert.assertEquals(Level.ALL, converter.toLevel(CustomLevel.ALL_P_1));
+    }
+
+    @Test
+    public void testCustomJulLevelNearFinest() {
+        // Sanity check:
+        Assert.assertEquals(LevelTranslator.FINEST, 
converter.toLevel(java.util.logging.Level.FINEST));
+        // Test:
+        Assert.assertEquals(LevelTranslator.FINEST, 
converter.toLevel(CustomLevel.FINEST_P_1));
+        Assert.assertEquals(LevelTranslator.FINEST, 
converter.toLevel(CustomLevel.FINEST_M_1));
+    }
+
+    @Test
+    public void testCustomJulLevelNearFiner() {
+        // Sanity check:
+        Assert.assertEquals(Level.TRACE, 
converter.toLevel(java.util.logging.Level.FINER));
+        // Test:
+        Assert.assertEquals(Level.TRACE, 
converter.toLevel(CustomLevel.FINER_P_1));
+        Assert.assertEquals(Level.TRACE, 
converter.toLevel(CustomLevel.FINER_M_1));
+    }
+
+    @Test
+    public void testCustomJulLevelNearFine() {
+        // Sanity check:
+        Assert.assertEquals(Level.DEBUG, 
converter.toLevel(java.util.logging.Level.FINE));
+        // Test:
+        Assert.assertEquals(Level.DEBUG, 
converter.toLevel(CustomLevel.FINE_P_1));
+        Assert.assertEquals(Level.DEBUG, 
converter.toLevel(CustomLevel.FINE_M_1));
+    }
+
+    @Test
+    public void testCustomJulLevelNearConfig() {
+        // Sanity check:
+        Assert.assertEquals(LevelTranslator.CONFIG, 
converter.toLevel(java.util.logging.Level.CONFIG));
+        // Test:
+        Assert.assertEquals(LevelTranslator.CONFIG, 
converter.toLevel(CustomLevel.CONFIG_P_1));
+        Assert.assertEquals(LevelTranslator.CONFIG, 
converter.toLevel(CustomLevel.CONFIG_M_1));
+    }
+
+    @Test
+    public void testCustomJulLevelNearInfo() {
+        // Sanity check:
+        Assert.assertEquals(Level.INFO, 
converter.toLevel(java.util.logging.Level.INFO));
+        // Test:
+        Assert.assertEquals(Level.INFO, 
converter.toLevel(CustomLevel.INFO_P_1));
+        Assert.assertEquals(Level.INFO, 
converter.toLevel(CustomLevel.INFO_M_1));
+    }
+
+    @Test
+    public void testCustomJulLevelNearWarning() {
+        // Sanity check:
+        Assert.assertEquals(Level.WARN, 
converter.toLevel(java.util.logging.Level.WARNING));
+        // Test:
+        Assert.assertEquals(Level.WARN, 
converter.toLevel(CustomLevel.WARNING_P_1));
+        Assert.assertEquals(Level.WARN, 
converter.toLevel(CustomLevel.WARNING_M_1));
+    }
+
+    @Test
+    public void testCustomJulLevelNearSevere() {
+        // Sanity check:
+        Assert.assertEquals(Level.ERROR, 
converter.toLevel(java.util.logging.Level.SEVERE));
+        // Test:
+        Assert.assertEquals(Level.ERROR, 
converter.toLevel(CustomLevel.SEVERE_P_1));
+        Assert.assertEquals(Level.ERROR, 
converter.toLevel(CustomLevel.SEVERE_M_1));
+    }
+
+    @Test
+    public void testCustomJulLevelNearOff() {
+        // Sanity check:
+        Assert.assertEquals(Level.OFF, 
converter.toLevel(java.util.logging.Level.OFF));
+        // Test:
+        Assert.assertEquals(Level.OFF, converter.toLevel(CustomLevel.OFF_M_1));
+    }
+}
diff --git 
a/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/DefaultLevelConverterTest.java
 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/DefaultLevelConverterTest.java
new file mode 100644
index 0000000..f93a4f4
--- /dev/null
+++ 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/DefaultLevelConverterTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.jul.tolog4j.test.internal;
+
+import static org.junit.Assert.assertNull;
+
+import org.apache.logging.jul.tolog4j.internal.DefaultLevelConverter;
+import org.junit.Test;
+
+public class DefaultLevelConverterTest {
+
+    /**
+     * (LOG4J2-1108) NullPointerException when passing null to 
java.util.logging.Logger.setLevel().
+     */
+    @Test
+    public void testJulSetNull() {
+        assertNull(new DefaultLevelConverter().toLevel(null));
+    }
+}
diff --git 
a/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/support/AbstractLoggerTest.java
 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/support/AbstractLoggerTest.java
new file mode 100644
index 0000000..9e620cd
--- /dev/null
+++ 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/support/AbstractLoggerTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.jul.tolog4j.test.support;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import java.util.logging.Logger;
+import org.apache.logging.jul.tolog4j.LevelTranslator;
+import org.apache.logging.jul.tolog4j.support.AbstractLogger;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.impl.MementoLogEvent;
+import org.apache.logging.log4j.core.test.appender.ListAppender;
+import org.junit.Test;
+
+/**
+ *
+ */
+public abstract class AbstractLoggerTest {
+    public static final String LOGGER_NAME = "Test";
+    protected Logger logger;
+    protected ListAppender eventAppender;
+    protected ListAppender flowAppender;
+    protected ListAppender stringAppender;
+
+    @Test
+    public void testGetName() {
+        assertThat(logger.getName()).isEqualTo(LOGGER_NAME);
+    }
+
+    @Test
+    public void testGlobalLogger() {
+        final Logger root = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
+        root.info("Test info message");
+        root.config("Test info message");
+        root.fine("Test info message");
+        final List<LogEvent> events = eventAppender.getEvents();
+        assertThat(events).hasSize(3);
+        for (final LogEvent event : events) {
+            final String message = event.getMessage().getFormattedMessage();
+            assertThat(message).isEqualTo("Test info message");
+        }
+    }
+
+    @Test
+    public void testGlobalLoggerName() {
+        final Logger root = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
+        assertThat(root.getName()).isEqualTo(Logger.GLOBAL_LOGGER_NAME);
+    }
+
+    @Test
+    public void testIsLoggable() {
+        assertThat(logger.isLoggable(java.util.logging.Level.SEVERE)).isTrue();
+    }
+
+    @Test
+    public void testLog() {
+        logger.info("Informative message here.");
+        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("Informative 
message here.");
+        
assertThat(event.getLoggerFqcn()).isEqualTo(AbstractLogger.class.getName());
+    }
+
+    @Test
+    public void testLogParamMarkers() {
+        final Logger flowLogger = Logger.getLogger("TestFlow");
+        flowLogger.logp(java.util.logging.Level.FINER, "sourceClass", 
"sourceMethod", "ENTER {0}", "params");
+        final List<LogEvent> events = flowAppender.getEvents();
+        
assertThat(events.get(0).getMessage().getFormattedMessage()).isEqualTo("ENTER 
params");
+    }
+
+    @Test
+    public void testLogUsingCustomLevel() {
+        logger.config("Config level");
+        final List<LogEvent> events = eventAppender.getEvents();
+        assertThat(events).hasSize(1);
+        final LogEvent event = events.get(0);
+        assertThat(event.getLevel()).isEqualTo(LevelTranslator.CONFIG);
+    }
+
+    @Test
+    public void testLogWithCallingClass() {
+        final Logger log = Logger.getLogger("Test.CallerClass");
+        log.config("Calling from LoggerTest");
+        final List<String> messages = stringAppender.getMessages();
+        assertThat(messages).hasSize(1);
+        final String message = messages.get(0);
+        assertThat(message).isEqualTo(AbstractLoggerTest.class.getName());
+    }
+
+    @Test
+    public void testCurlyBraces() {
+        testMessage("{message}");
+    }
+
+    @Test
+    public void testPercent() {
+        testMessage("message%s");
+    }
+
+    @Test
+    public void testPercentAndCurlyBraces() {
+        testMessage("message{%s}");
+    }
+
+    private void testMessage(final String string) {
+        final Logger root = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
+        root.info("Test info " + string);
+        root.config("Test info " + string);
+        root.fine("Test info " + string);
+        final List<LogEvent> events = eventAppender.getEvents();
+        assertThat(events).hasSize(3);
+        for (final LogEvent event : events) {
+            final String message = event.getMessage().getFormattedMessage();
+            assertThat(message).isEqualTo("Test info " + string);
+        }
+    }
+
+    @Test
+    public void testFlowMessages() {
+        final Logger flowLogger = Logger.getLogger("TestFlow");
+        flowLogger.entering("com.example.TestSourceClass1", 
"testSourceMethod1(String)");
+        flowLogger.entering("com.example.TestSourceClass2", 
"testSourceMethod2(String)", "TestParam");
+        flowLogger.entering(
+                "com.example.TestSourceClass3", "testSourceMethod3(String)", 
new Object[] {"TestParam0", "TestParam1"});
+        final List<LogEvent> events = flowAppender.getEvents();
+        assertThat(events).hasSize(3);
+        
assertThat(events.get(0).getMessage().getFormattedMessage()).isEqualTo("Enter");
+        
assertThat(events.get(1).getMessage().getFormattedMessage()).isEqualTo("Enter 
params(TestParam)");
+        
assertThat(events.get(2).getMessage().getFormattedMessage()).isEqualTo("Enter 
params(TestParam0, TestParam1)");
+    }
+
+    @Test
+    public void testLambdasGlobalLogger() {
+        testLambdaMessages("message");
+    }
+
+    @Test
+    public void testLambdasCurlyBraces() {
+        testLambdaMessages("{message}");
+    }
+
+    @Test
+    public void testLambdasPercent() {
+        testLambdaMessages("message%s");
+    }
+
+    @Test
+    public void testLambdasPercentAndCurlyBraces() {
+        testLambdaMessages("message{%s}");
+    }
+
+    private void testLambdaMessages(final String string) {
+        final Logger root = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
+        root.info(() -> "Test info " + string);
+        root.config(() -> "Test info " + string);
+        root.fine(() -> "Test info " + string);
+        final List<LogEvent> events = eventAppender.getEvents();
+        assertThat(events).hasSize(3);
+        for (final LogEvent event : events) {
+            final String message = event.getMessage().getFormattedMessage();
+            assertThat(message).isEqualTo("Test info " + string);
+        }
+    }
+}
diff --git 
a/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/support/CustomLoggerAdapterTest.java
 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/support/CustomLoggerAdapterTest.java
new file mode 100644
index 0000000..83d90f3
--- /dev/null
+++ 
b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/support/CustomLoggerAdapterTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.jul.tolog4j.test.support;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.logging.Filter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.logging.jul.tolog4j.LogManager;
+import org.apache.logging.jul.tolog4j.spi.AbstractLoggerAdapter;
+import org.apache.logging.jul.tolog4j.support.AbstractLogger;
+import org.apache.logging.jul.tolog4j.test.JulTestProperties;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+import org.apache.logging.log4j.spi.LoggerContext;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests if the logger adapter can be customized.
+ */
+public class CustomLoggerAdapterTest {
+
+    private static final org.apache.logging.log4j.Logger LOGGER = 
StatusLogger.getLogger();
+
+    @BeforeClass
+    public static void setUpClass() {
+        System.setProperty("java.util.logging.manager", 
LogManager.class.getName());
+        System.setProperty(JulTestProperties.JUL_LOGGER_ADAPTER, 
CustomLoggerAdapter.class.getName());
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+        System.clearProperty("java.util.logging.manager");
+        System.clearProperty(JulTestProperties.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
+        public Logger newLogger(String name, LoggerContext context) {
+            return new CustomLogger(context.getLogger(name));
+        }
+    }
+
+    private static class CustomLogger extends AbstractLogger {
+
+        CustomLogger(ExtendedLogger logger) {
+            super(logger);
+        }
+
+        @Override
+        public void setFilter(Filter newFilter) {}
+
+        @Override
+        public void setLevel(final Level newLevel) throws SecurityException {
+            LOGGER.error("Cannot set JUL log level through Log4j API: ignoring 
call to Logger.setLevel({})", newLevel);
+        }
+
+        @Override
+        public void addHandler(Handler handler) {}
+
+        @Override
+        public void removeHandler(Handler handler) {}
+
+        @Override
+        public void setUseParentHandlers(boolean useParentHandlers) {}
+
+        @Override
+        public void setParent(Logger parent) {}
+    }
+}
diff --git a/jul-to-log4j/src/test/resources/CallerInformationTest.xml 
b/jul-to-log4j/src/test/resources/CallerInformationTest.xml
new file mode 100644
index 0000000..087587e
--- /dev/null
+++ b/jul-to-log4j/src/test/resources/CallerInformationTest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<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="ALL">
+      <AppenderRef ref="Class"/>
+    </Logger>
+    <Logger name="MethodLogger" level="ALL">
+      <AppenderRef ref="Method"/>
+    </Logger>
+    <Root level="OFF"/>
+  </Loggers>
+</Configuration>
diff --git a/jul-to-log4j/src/test/resources/ResourceBundleTest.properties 
b/jul-to-log4j/src/test/resources/ResourceBundleTest.properties
new file mode 100644
index 0000000..3563a11
--- /dev/null
+++ b/jul-to-log4j/src/test/resources/ResourceBundleTest.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.
+#
+##
+# Resource bundle used in ResourceBundleTest
+
+msg_1 = Hello!
+msg_2 = Hello %s and %s!
diff --git a/jul-to-log4j/src/test/resources/ResourceBundleTest.xml 
b/jul-to-log4j/src/test/resources/ResourceBundleTest.xml
new file mode 100644
index 0000000..3d2d891
--- /dev/null
+++ b/jul-to-log4j/src/test/resources/ResourceBundleTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<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="LIST"/>
+  </Appenders>
+  <Loggers>
+    <Root level="INFO">
+      <AppenderRef ref="LIST"/>
+    </Root>
+  </Loggers>
+</Configuration>
diff --git a/jul-to-log4j/src/test/resources/log4j2-test.xml 
b/jul-to-log4j/src/test/resources/log4j2-test.xml
new file mode 100644
index 0000000..9c5bdb2
--- /dev/null
+++ b/jul-to-log4j/src/test/resources/log4j2-test.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<Configuration name="LoggerTest" status="OFF">
+  <Appenders>
+    <List name="TestAppender"/>
+    <List name="FlowAppender"/>
+    <List name="StringAppender">
+      <PatternLayout pattern="%class"/>
+    </List>
+    <Console name="Console" target="SYSTEM_ERR">
+      <PatternLayout pattern="%highlight{%p - %m%n}"/>
+    </Console>
+  </Appenders>
+  <Loggers>
+    <Logger name="TestFlow" level="ALL" additivity="false">
+      <AppenderRef ref="FlowAppender"/>
+    </Logger>
+    <Logger name="Test" level="DEBUG" additivity="false">
+      <AppenderRef ref="TestAppender"/>
+    </Logger>
+    <Logger name="Test.CallerClass" level="DEBUG" additivity="false">
+      <AppenderRef ref="StringAppender"/>
+    </Logger>
+    <Logger name="global" level="DEBUG" additivity="false">
+      <AppenderRef ref="TestAppender"/>
+    </Logger>
+    <Root level="ERROR">
+      <AppenderRef ref="Console"/>
+    </Root>
+  </Loggers>
+</Configuration>

Reply via email to