From 3ea53e1b41279f5a0eb61746542b9938e0c0a459 Mon Sep 17 00:00:00 2001
From: Ralph Goers
Date: Mon, 6 Feb 2012 23:55:13 +0000
Subject: [PATCH 1/4] Make new home for Log4j2
git-svn-id: https://svn.apache.org/repos/asf/logging/log4j/log4j2/trunk@1241269 13f79535-47bb-0310-9956-ffa450edef68
From f650a557036328b5c01e56aec2df642305edb45f Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 22 Oct 2024 18:57:22 +0200
Subject: [PATCH 2/4] 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 ++++
.../logging/jul/tolog4j/LogManager.java | 104 +++++
.../jul/tolog4j/internal/ApiLogger.java | 74 +++
.../tolog4j/internal/ApiLoggerAdapter.java | 39 ++
.../internal/DefaultLevelConverter.java | 129 +++++
.../jul/tolog4j/internal/JulProperties.java | 27 ++
.../jul/tolog4j/internal/NoOpLogger.java | 222 +++++++++
.../logging/jul/tolog4j/package-info.java | 22 +
.../tolog4j/spi/AbstractLoggerAdapter.java | 67 +++
.../jul/tolog4j/spi/LevelConverter.java | 49 ++
.../logging/jul/tolog4j/spi/package-info.java | 25 +
.../jul/tolog4j/support/AbstractLogger.java | 441 ++++++++++++++++++
.../jul/tolog4j/support/package-info.java | 26 ++
.../META-INF/log4j/propertyMapping.json | 10 +
.../tolog4j/test/AsyncLoggerThreadsTest.java | 59 +++
.../BracketInNotInterpolatedMessageTest.java | 58 +++
.../tolog4j/test/CallerInformationTest.java | 162 +++++++
.../tolog4j/test/JavaLevelTranslatorTest.java | 72 +++
.../jul/tolog4j/test/JulTestProperties.java | 24 +
.../test/Log4jLevelTranslatorTest.java | 65 +++
.../jul/tolog4j/test/ResourceBundleTest.java | 91 ++++
.../tolog4j/test/internal/ApiLoggerTest.java | 91 ++++
...aultLevelConverterCustomJulLevelsTest.java | 144 ++++++
.../internal/DefaultLevelConverterTest.java | 33 ++
.../test/support/AbstractLoggerTest.java | 185 ++++++++
.../test/support/CustomLoggerAdapterTest.java | 95 ++++
.../test/resources/CallerInformationTest.xml | 42 ++
.../resources/ResourceBundleTest.properties | 21 +
.../src/test/resources/ResourceBundleTest.xml | 31 ++
.../src/test/resources/log4j2-test.xml | 46 ++
31 files changed, 2675 insertions(+)
create mode 100644 jul-to-log4j/pom.xml
create mode 100644 jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/LevelTranslator.java
create mode 100644 jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/LogManager.java
create mode 100644 jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/ApiLogger.java
create mode 100644 jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/ApiLoggerAdapter.java
create mode 100644 jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/DefaultLevelConverter.java
create mode 100644 jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/JulProperties.java
create mode 100644 jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/NoOpLogger.java
create mode 100644 jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/package-info.java
create mode 100644 jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/AbstractLoggerAdapter.java
create mode 100644 jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/LevelConverter.java
create mode 100644 jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/package-info.java
create mode 100644 jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/support/AbstractLogger.java
create mode 100644 jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/support/package-info.java
create mode 100644 jul-to-log4j/src/main/resources/META-INF/log4j/propertyMapping.json
create mode 100644 jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/AsyncLoggerThreadsTest.java
create mode 100644 jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/BracketInNotInterpolatedMessageTest.java
create mode 100644 jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/CallerInformationTest.java
create mode 100644 jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/JavaLevelTranslatorTest.java
create mode 100644 jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/JulTestProperties.java
create mode 100644 jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/Log4jLevelTranslatorTest.java
create mode 100644 jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/ResourceBundleTest.java
create mode 100644 jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/ApiLoggerTest.java
create mode 100644 jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/DefaultLevelConverterCustomJulLevelsTest.java
create mode 100644 jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/DefaultLevelConverterTest.java
create mode 100644 jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/support/AbstractLoggerTest.java
create mode 100644 jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/support/CustomLoggerAdapterTest.java
create mode 100644 jul-to-log4j/src/test/resources/CallerInformationTest.xml
create mode 100644 jul-to-log4j/src/test/resources/ResourceBundleTest.properties
create mode 100644 jul-to-log4j/src/test/resources/ResourceBundleTest.xml
create mode 100644 jul-to-log4j/src/test/resources/log4j2-test.xml
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 @@
+
+
+
+ 4.0.0
+
+ org.apache.logging.log4j
+ log4j
+ ${revision}
+ ../log4j-parent
+
+
+ jul-to-log4j
+ Apache Log4j JUL LogManager
+ A `java.util.logging` LogManager that forwards events to the Log4j API.
+
+
+
+
+
+ org.jspecify.*;resolution:=optional
+
+
+
+ org.jspecify;transitive=false
+
+
+
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+
+
+
+ org.apache.logging.log4j
+ log4j-kit
+
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+ org.hamcrest
+ hamcrest
+ test
+
+
+
+ junit
+ junit
+ test
+
+
+
+ org.apache.logging.log4j
+ log4j-async-logger
+ test
+
+
+
+ org.apache.logging.log4j
+ log4j-core
+ test
+
+
+
+ org.apache.logging.log4j
+ log4j-core-test
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ true
+
+ -Xms256m -Xmx1024m
+ 1
+ false
+
+
+
+
+ org.apache.maven.surefire
+ surefire-junit47
+ ${surefire.version}
+
+
+
+
+ default-test
+
+ test
+
+ test
+
+
+
+ org.apache.logging.jul.tolog4j.LogManager
+
+
+
+
+
+
+
+
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}.
+ *
+ * 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.
+ *
+ *
+ * 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.
+ *
+ *
+ * @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> 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 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 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.
+ *
+ * Since 2.4, supports custom JUL levels by mapping them to their closest mapped neighbour.
+ *
+ *
+ * @since 2.1
+ */
+public class DefaultLevelConverter implements LevelConverter {
+
+ static final class JulLevelComparator implements Comparator {
+ @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 julToLog4j = new ConcurrentHashMap<>(9);
+ private final Map log4jToJul = new IdentityHashMap<>(10);
+ private final List 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 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 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 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 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 msgSupplier) {}
+
+ @Override
+ public void warning(final Supplier msgSupplier) {}
+
+ @Override
+ public void info(final Supplier msgSupplier) {}
+
+ @Override
+ public void config(final Supplier msgSupplier) {}
+
+ @Override
+ public void fine(final Supplier msgSupplier) {}
+
+ @Override
+ public void finer(final Supplier msgSupplier) {}
+
+ @Override
+ public void finest(final Supplier 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.
+ *
+ * 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.
+ *
+ *
+ * Older Log4j versions provided an alternative {@code CoreLoggerAdapter} implementation that supported
+ * the modification of Log4j Core configuration using JUL.
+ *
+ * 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.
+ *
+ *
+ * Implementation note: since version 3.0.0, this interface was moved to a new package.
+ *
+ * Each implementation should provide this method.
+ *
+ */
+ @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}.
+ *
+ * Implementation note: since version 3.0.0, this interface was moved to a new package.
+ *
+ *
+ * @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.
+ *
+ * Note that this implementation does not use the {@link java.util.logging.Handler} class.
+ * 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.
+ *
+ *
+ * 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}.
+ *
+ *
+ * @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);
+ }
+
+ //
+ // 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.
+ *
+ * Implementation note: this method returns the level explicitly configured
+ * in the Log4j API logging implementation and is implementation specific.
+ * The default implementation always returns {@code null}.
+ *
+ *
+ * To test if a logger is enabled for a specific logging level, i.e. to test its effective
+ * level, use {@link Logger#isLoggable(Level)}.
+ *
+ * @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;
+ }
+
+ //
+
+ //
+ // 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 toLog4jSupplier(Supplier msgSupplier) {
+ return msgSupplier::get;
+ }
+
+ private org.apache.logging.log4j.util.Supplier toMessageSupplier(Supplier msgSupplier) {
+ return () -> logger.getMessageFactory().newMessage(msgSupplier.get());
+ }
+
+ private org.apache.logging.log4j.util.Supplier toMessageSupplier(ResourceBundle bundle, String msg) {
+ return () -> new LocalizedMessage(bundle, msg);
+ }
+
+ private org.apache.logging.log4j.util.Supplier 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 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 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 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 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 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 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 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 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 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 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 msgSupplier) {
+ logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, toLog4jSupplier(msgSupplier), null);
+ }
+ //
+}
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 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 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 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 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