+ implements BeforeAllCallback, BeforeEachCallback {
+
+ private static final Object KEY = LoggerContextHolder.class;
+
+ @Override
+ public void beforeEach(ExtensionContext extensionContext) throws Exception {
+ final Class> testClass = extensionContext.getRequiredTestClass();
+ if (AnnotationSupport.isAnnotated(testClass, LoggerContextSource.class)) {
+ final LoggerContextHolder holder =
+ ExtensionContextAnchor.getAttribute(KEY, LoggerContextHolder.class, extensionContext);
+ if (holder == null) {
+ throw new IllegalStateException(
+ "Specified @LoggerContextSource but no LoggerContext found for test class "
+ + testClass.getCanonicalName());
+ }
+ }
+ AnnotationSupport.findAnnotation(extensionContext.getRequiredTestMethod(), LoggerContextSource.class)
+ .ifPresent(source -> {
+ final LoggerContextHolder holder = new LoggerContextHolder(source, extensionContext);
+ ExtensionContextAnchor.setAttribute(KEY, holder, extensionContext);
+ });
+ final LoggerContextHolder holder =
+ ExtensionContextAnchor.getAttribute(KEY, LoggerContextHolder.class, extensionContext);
+ if (holder != null) {
+ ReflectionSupport.findFields(
+ extensionContext.getRequiredTestClass(),
+ f -> ModifierSupport.isNotStatic(f) && f.getType().equals(LoggerContext.class),
+ HierarchyTraversalMode.TOP_DOWN)
+ .forEach(f -> {
+ try {
+ f.setAccessible(true);
+ f.set(extensionContext.getRequiredTestInstance(), holder.getLoggerContext());
+ } catch (ReflectiveOperationException e) {
+ throw new ExtensionContextException("Failed to inject field " + f, e);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void beforeAll(ExtensionContext extensionContext) throws Exception {
+ final Class> testClass = extensionContext.getRequiredTestClass();
+ AnnotationSupport.findAnnotation(testClass, LoggerContextSource.class).ifPresent(testSource -> {
+ final LoggerContextHolder holder = new LoggerContextHolder(testSource, extensionContext);
+ ExtensionContextAnchor.setAttribute(KEY, holder, extensionContext);
+ });
+ final LoggerContextHolder holder =
+ ExtensionContextAnchor.getAttribute(KEY, LoggerContextHolder.class, extensionContext);
+ if (holder != null) {
+ ReflectionSupport.findFields(
+ extensionContext.getRequiredTestClass(),
+ f -> ModifierSupport.isStatic(f) && f.getType().equals(LoggerContext.class),
+ HierarchyTraversalMode.TOP_DOWN)
+ .forEach(f -> {
+ try {
+ f.setAccessible(true);
+ f.set(null, holder.getLoggerContext());
+ } catch (ReflectiveOperationException e) {
+ throw new ExtensionContextException("Failed to inject field " + f, e);
+ }
+ });
+ }
+ }
+
+ @Override
+ public LoggerContext resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
+ throws ParameterResolutionException {
+ return ExtensionContextAnchor.getAttribute(KEY, LoggerContextHolder.class, extensionContext)
+ .getLoggerContext();
+ }
+
+ static final class LoggerContextHolder implements Store.CloseableResource {
+
+ private final LoggerContext context;
+ private final Logger logger;
+
+ private LoggerContextHolder(final LoggerContextSource source, final ExtensionContext extensionContext) {
+ this.context = (LoggerContext) LoggerFactory.getILoggerFactory();
+ Class> clazz = extensionContext.getRequiredTestClass();
+ this.logger = context.getLogger(clazz);
+
+ final JoranConfigurator configurator = new JoranConfigurator();
+ final URL configLocation = getConfigLocation(source, extensionContext);
+ configurator.setContext(context);
+ try {
+ configurator.doConfigure(configLocation);
+ } catch (final JoranException e) {
+ throw new ExtensionContextException("Failed to initialize Logback logger context for " + clazz, e);
+ }
+ }
+
+ private static URL getConfigLocation(
+ final LoggerContextSource source, final ExtensionContext extensionContext) {
+ final String value = source.value();
+ Class> clazz = extensionContext.getRequiredTestClass();
+ URL url = null;
+ if (value.isEmpty()) {
+ while (clazz != null) {
+ url = clazz.getResource(clazz.getSimpleName() + ".xml");
+ if (url != null) {
+ break;
+ }
+ clazz = clazz.getSuperclass();
+ }
+ } else {
+ url = clazz.getClassLoader().getResource(value);
+ }
+ if (url != null) {
+ return url;
+ }
+ throw new ExtensionContextException("Failed to find a default configuration for " + clazz);
+ }
+
+ public LoggerContext getLoggerContext() {
+ return context;
+ }
+
+ public Logger getLogger() {
+ return logger;
+ }
+
+ @Override
+ public void close() {
+ context.stop();
+ }
+ }
+}
diff --git a/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerContextSource.java b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerContextSource.java
new file mode 100644
index 0000000..d71921c
--- /dev/null
+++ b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerContextSource.java
@@ -0,0 +1,52 @@
+/*
+ * 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.slf4j;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apache.logging.log4j.test.junit.TempLoggingDirectory;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Specifies a configuration file to use for unit tests.
+ *
+ * This is similar to the org.apache.logging.log4j.core.junit.LoggerContextSource annotation for Log4j.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Documented
+@Inherited
+@Tag("functional")
+@ExtendWith(TempLoggingDirectory.class)
+@ExtendWith(LoggerContextResolver.class)
+@ExtendWith(LoggerResolver.class)
+public @interface LoggerContextSource {
+ /**
+ * Specifies the name of the configuration file to use for the annotated test.
+ *
+ * Defaults to the fully qualified name of the test class with '.xml' appended.
+ * E.g. this class would have a default of
+ * {@code org/apache/logging/log4j/core/test/junit/LoggerContextSource.xml}.
+ *
+ */
+ String value() default "";
+}
diff --git a/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerResolver.java b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerResolver.java
new file mode 100644
index 0000000..28f9411
--- /dev/null
+++ b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerResolver.java
@@ -0,0 +1,84 @@
+/*
+ * 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.slf4j;
+
+import ch.qos.logback.classic.Logger;
+import org.apache.logging.log4j.test.junit.ExtensionContextAnchor;
+import org.apache.logging.slf4j.LoggerContextResolver.LoggerContextHolder;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ExtensionContextException;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.support.TypeBasedParameterResolver;
+import org.junit.platform.commons.support.HierarchyTraversalMode;
+import org.junit.platform.commons.support.ModifierSupport;
+import org.junit.platform.commons.support.ReflectionSupport;
+
+public class LoggerResolver extends TypeBasedParameterResolver
+ implements BeforeAllCallback, BeforeEachCallback {
+
+ private static final Object KEY = LoggerContextHolder.class;
+
+ @Override
+ public Logger resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
+ throws ParameterResolutionException {
+ return ExtensionContextAnchor.getAttribute(KEY, LoggerContextHolder.class, extensionContext)
+ .getLogger();
+ }
+
+ @Override
+ public void beforeAll(ExtensionContext extensionContext) throws Exception {
+ final LoggerContextHolder holder =
+ ExtensionContextAnchor.getAttribute(KEY, LoggerContextHolder.class, extensionContext);
+ if (holder != null) {
+ ReflectionSupport.findFields(
+ extensionContext.getRequiredTestClass(),
+ f -> ModifierSupport.isStatic(f) && f.getType().equals(Logger.class),
+ HierarchyTraversalMode.TOP_DOWN)
+ .forEach(f -> {
+ try {
+ f.setAccessible(true);
+ f.set(null, holder.getLogger());
+ } catch (ReflectiveOperationException e) {
+ throw new ExtensionContextException("Failed to inject field " + f, e);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void beforeEach(ExtensionContext extensionContext) throws Exception {
+ final LoggerContextHolder holder =
+ ExtensionContextAnchor.getAttribute(KEY, LoggerContextHolder.class, extensionContext);
+ if (holder != null) {
+ ReflectionSupport.findFields(
+ extensionContext.getRequiredTestClass(),
+ f -> ModifierSupport.isNotStatic(f) && f.getType().equals(Logger.class),
+ HierarchyTraversalMode.TOP_DOWN)
+ .forEach(f -> {
+ try {
+ f.setAccessible(true);
+ f.set(extensionContext.getRequiredTestInstance(), holder.getLogger());
+ } catch (ReflectiveOperationException e) {
+ throw new ExtensionContextException("Failed to inject field " + f, e);
+ }
+ });
+ }
+ }
+}
diff --git a/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java
new file mode 100644
index 0000000..c6a3cec
--- /dev/null
+++ b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java
@@ -0,0 +1,268 @@
+/*
+ * 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.slf4j;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.theInstance;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.testUtil.StringListAppender;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Proxy;
+import java.util.Date;
+import java.util.List;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.message.MessageFactory;
+import org.apache.logging.log4j.message.ParameterizedMessageFactory;
+import org.apache.logging.log4j.message.StringFormatterMessageFactory;
+import org.apache.logging.log4j.spi.AbstractLogger;
+import org.apache.logging.log4j.spi.MessageFactory2Adapter;
+import org.apache.logging.log4j.test.junit.UsingStatusListener;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.MDC;
+
+@UsingStatusListener
+@LoggerContextSource
+public class LoggerTest {
+
+ private static final Object OBJ = new Object();
+ // Log4j objects
+ private Logger logger;
+ // Logback objects
+ private LoggerContext context;
+ private StringListAppender list;
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ final org.slf4j.Logger slf4jLogger = context.getLogger(getClass());
+ logger = LogManager.getLogger();
+ assertThat(slf4jLogger, is(theInstance(((SLF4JLogger) logger).getLogger())));
+ final ch.qos.logback.classic.Logger rootLogger = context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
+ rootLogger.detachAppender("console");
+ list = TestUtil.getListAppender(rootLogger, "LIST");
+ assertThat(list, is(notNullValue()));
+ assertThat(list.strList, is(notNullValue()));
+ list.strList.clear();
+ }
+
+ @Test
+ public void basicFlow() {
+ logger.traceEntry();
+ logger.traceExit();
+ assertThat(list.strList, hasSize(2));
+ }
+
+ @Test
+ public void basicFlowDepreacted() {
+ logger.entry();
+ logger.exit();
+ assertThat(list.strList, hasSize(2));
+ }
+
+ @Test
+ public void simpleFlowDeprecated() {
+ logger.entry(OBJ);
+ logger.exit(0);
+ assertThat(list.strList, hasSize(2));
+ }
+
+ @Test
+ public void simpleFlow() {
+ logger.entry(OBJ);
+ logger.traceExit(0);
+ assertThat(list.strList, hasSize(2));
+ }
+
+ @Test
+ public void throwing() {
+ logger.throwing(new IllegalArgumentException("Test Exception"));
+ assertThat(list.strList, hasSize(1));
+ }
+
+ @Test
+ public void catching() {
+ try {
+ throw new NullPointerException();
+ } catch (final Exception e) {
+ logger.catching(e);
+ }
+ assertThat(list.strList, hasSize(1));
+ }
+
+ @Test
+ public void debug() {
+ logger.debug("Debug message");
+ assertThat(list.strList, hasSize(1));
+ }
+
+ @Test
+ public void getLogger_String_MessageFactoryMismatch() {
+ final Logger testLogger = testMessageFactoryMismatch(
+ "getLogger_String_MessageFactoryMismatch",
+ StringFormatterMessageFactory.INSTANCE,
+ ParameterizedMessageFactory.INSTANCE);
+ testLogger.debug("%,d", Integer.MAX_VALUE);
+ assertThat(list.strList, hasSize(1));
+ assertThat(list.strList, hasItem(String.format("%,d", Integer.MAX_VALUE)));
+ }
+
+ @Test
+ public void getLogger_String_MessageFactoryMismatchNull() {
+ final Logger testLogger = testMessageFactoryMismatch(
+ "getLogger_String_MessageFactoryMismatchNull", StringFormatterMessageFactory.INSTANCE, null);
+ testLogger.debug("%,d", Integer.MAX_VALUE);
+ assertThat(list.strList, hasSize(1));
+ assertThat(list.strList, hasItem(String.format("%,d", Integer.MAX_VALUE)));
+ }
+
+ private Logger testMessageFactoryMismatch(
+ final String name, final MessageFactory messageFactory1, final MessageFactory messageFactory2) {
+ final Logger testLogger = LogManager.getLogger(name, messageFactory1);
+ assertThat(testLogger, is(notNullValue()));
+ checkMessageFactory(messageFactory1, testLogger);
+ final Logger testLogger2 = LogManager.getLogger(name, messageFactory2);
+ checkMessageFactory(messageFactory2, testLogger2);
+ return testLogger;
+ }
+
+ private static void checkMessageFactory(final MessageFactory messageFactory1, final Logger testLogger1) {
+ if (messageFactory1 == null) {
+ assertEquals(
+ AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS,
+ testLogger1.getMessageFactory().getClass());
+ } else {
+ MessageFactory actual = testLogger1.getMessageFactory();
+ if (actual instanceof MessageFactory2Adapter) {
+ actual = ((MessageFactory2Adapter) actual).getOriginal();
+ }
+ assertEquals(messageFactory1, actual);
+ }
+ }
+
+ @Test
+ public void debugObject() {
+ logger.debug(new Date());
+ assertThat(list.strList, hasSize(1));
+ }
+
+ @Test
+ public void debugWithParms() {
+ logger.debug("Hello, {}", "World");
+ assertThat(list.strList, hasSize(1));
+ final String message = list.strList.get(0);
+ assertEquals("Hello, World", message);
+ }
+
+ @Test
+ public void paramIncludesSubstitutionMarker_locationAware() {
+ logger.info("Hello, {}", "foo {} bar");
+ assertThat(list.strList, hasSize(1));
+ final String message = list.strList.get(0);
+ assertEquals("Hello, foo {} bar", message);
+ }
+
+ @Test
+ public void paramIncludesSubstitutionMarker_nonLocationAware() {
+ final org.slf4j.Logger slf4jLogger = context.getLogger(getClass());
+ final Logger nonLocationAwareLogger =
+ new SLF4JLogger(slf4jLogger.getName(), (org.slf4j.Logger) Proxy.newProxyInstance(
+ getClass().getClassLoader(), new Class>[] {org.slf4j.Logger.class}, (proxy, method, args) -> {
+ try {
+ return method.invoke(slf4jLogger, args);
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ }));
+ nonLocationAwareLogger.info("Hello, {}", "foo {} bar");
+ assertThat(list.strList, hasSize(1));
+ final String message = list.strList.get(0);
+ assertEquals("Hello, foo {} bar", message);
+ }
+
+ @Test
+ public void testImpliedThrowable() {
+ logger.debug("This is a test", new Throwable("Testing"));
+ final List msgs = list.strList;
+ assertThat(msgs, hasSize(1));
+ final String expected = "java.lang.Throwable: Testing";
+ assertTrue(msgs.get(0).contains(expected), "Incorrect message data");
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void mdc() {
+ ThreadContext.put("TestYear", Integer.toString(2010));
+ logger.debug("Debug message");
+ ThreadContext.clearMap();
+ logger.debug("Debug message");
+ assertThat(list.strList, hasSize(2));
+ assertTrue(list.strList.get(0).startsWith("2010"), "Incorrect year");
+ }
+
+ @Test
+ public void mdcNullBackedIsEmpty() {
+ assertNull(MDC.getCopyOfContextMap(), "Setup wrong");
+ assertTrue(ThreadContext.isEmpty());
+ }
+
+ @Test
+ public void mdcNullBackedContainsKey() {
+ assertNull(MDC.getCopyOfContextMap(), "Setup wrong");
+ assertFalse(ThreadContext.containsKey("something"));
+ }
+
+ @Test
+ public void mdcNullBackedContainsNullKey() {
+ assertNull(MDC.getCopyOfContextMap(), "Setup wrong");
+ assertFalse(ThreadContext.containsKey(null));
+ }
+
+ @Test
+ public void mdcContainsNullKey() {
+ try {
+ ThreadContext.put("some", "thing");
+ assertNotNull(MDC.getCopyOfContextMap(), "Setup wrong");
+ assertFalse(ThreadContext.containsKey(null));
+ } finally {
+ ThreadContext.clearMap();
+ }
+ }
+
+ @Test
+ public void mdcCannotContainNullKey() {
+ try {
+ ThreadContext.put(null, "something");
+ fail("should throw");
+ } catch (IllegalArgumentException | NullPointerException e) {
+ // expected
+ }
+ }
+}
diff --git a/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/MDCContextMapTest.java b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/MDCContextMapTest.java
new file mode 100644
index 0000000..6673de4
--- /dev/null
+++ b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/MDCContextMapTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.slf4j;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import org.apache.logging.log4j.spi.ThreadContextMap;
+import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.Issue;
+import org.slf4j.MDCTestHelper;
+import org.slf4j.spi.MDCAdapter;
+
+class MDCContextMapTest {
+
+ @Test
+ @Issue("https://github.com/apache/logging-log4j2/issues/1426")
+ void nonNullGetCopy() {
+ final ThreadContextMap contextMap = new MDCContextMap();
+ final MDCAdapter mockAdapter = mock(MDCAdapter.class);
+ when(mockAdapter.getCopyOfContextMap()).thenReturn(null);
+ final MDCAdapter adapter = MDCTestHelper.replaceMDCAdapter(mockAdapter);
+ try {
+ assertThat(contextMap.getImmutableMapOrNull()).isNull();
+ assertThat(contextMap.getCopy()).isNotNull();
+ verify(mockAdapter, times(2)).getCopyOfContextMap();
+ verifyNoMoreInteractions(mockAdapter);
+ } finally {
+ MDCTestHelper.replaceMDCAdapter(adapter);
+ }
+ }
+}
diff --git a/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/TestUtil.java b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/TestUtil.java
new file mode 100644
index 0000000..afcd9a8
--- /dev/null
+++ b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/TestUtil.java
@@ -0,0 +1,47 @@
+/*
+ * 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.slf4j;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.spi.AppenderAttachable;
+import ch.qos.logback.core.testUtil.StringListAppender;
+import org.slf4j.Logger;
+
+/**
+ * Utility methods for unit tests integrating with Logback.
+ *
+ * @since 2.1
+ */
+public final class TestUtil {
+
+ public static StringListAppender getListAppender(final SLF4JLogger slf4jLogger, final String name) {
+ final Logger logger = slf4jLogger.getLogger();
+ if (!(logger instanceof AppenderAttachable)) {
+ throw new AssertionError("SLF4JLogger.getLogger() did not return an instance of AppenderAttachable");
+ }
+ @SuppressWarnings("unchecked")
+ final AppenderAttachable attachable = (AppenderAttachable) logger;
+ return getListAppender(attachable, name);
+ }
+
+ public static StringListAppender getListAppender(
+ final AppenderAttachable logger, final String name) {
+ return (StringListAppender) logger.getAppender(name);
+ }
+
+ private TestUtil() {}
+}
diff --git a/log4j-api-to-slf4j/src/test/java/org/slf4j/MDCTestHelper.java b/log4j-api-to-slf4j/src/test/java/org/slf4j/MDCTestHelper.java
new file mode 100644
index 0000000..0256131
--- /dev/null
+++ b/log4j-api-to-slf4j/src/test/java/org/slf4j/MDCTestHelper.java
@@ -0,0 +1,28 @@
+/*
+ * 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.slf4j;
+
+import org.slf4j.spi.MDCAdapter;
+
+public class MDCTestHelper {
+
+ public static MDCAdapter replaceMDCAdapter(final MDCAdapter adapter) {
+ final MDCAdapter old = MDC.mdcAdapter;
+ MDC.mdcAdapter = adapter;
+ return old;
+ }
+}
diff --git a/log4j-api-to-slf4j/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider b/log4j-api-to-slf4j/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
new file mode 100644
index 0000000..d0396bb
--- /dev/null
+++ b/log4j-api-to-slf4j/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
@@ -0,0 +1,17 @@
+# 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
+#
+# https://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.
+##
+# BND can generate this, but we need it faster
+org.apache.logging.slf4j.SLF4JProvider
\ No newline at end of file
diff --git a/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/CallerInformationTest.xml b/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/CallerInformationTest.xml
new file mode 100644
index 0000000..9cef52d
--- /dev/null
+++ b/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/CallerInformationTest.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+ %class
+
+
+
+
+
+
+
+
+ %method
+
+
+
+
+
+
diff --git a/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/LogBuilderTest.xml b/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/LogBuilderTest.xml
new file mode 100644
index 0000000..9305cc8
--- /dev/null
+++ b/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/LogBuilderTest.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+ ACCEPT
+ callerId
+ INFO
+
+ Log4j2
+ TRACE
+
+
+
+
+
+ %msg
+
+
+
+
+
+
+
diff --git a/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/LoggerTest.xml b/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/LoggerTest.xml
new file mode 100644
index 0000000..ec38ed6
--- /dev/null
+++ b/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/LoggerTest.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+ %X{TestYear}%msg
+
+
+
+
+
+
+
diff --git a/parent/pom.xml b/parent/pom.xml
index 5a1db4f..cfb8791 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -31,9 +31,19 @@
https://logging.apache.org/log4j/2.x/manual/installation.html
+
+ false
+ 3.26.3
+ 7.0.0
+ 2.2
+ 1.0.0
+ 5.11.3
2.24.1
+ 5.14.2
+ 2.0.0
+ 1.1.2
2.0.16
@@ -41,19 +51,76 @@
+
+ org.assertj
+ assertj-bom
+ ${assertj.version}
+ pom
+ import
+
+
+
+ org.junit
+ junit-bom
+ ${junit.version}
+ pom
+ import
+
+
org.apache.logging.log4j
- log4j-api
+ log4j-bom
${log4j.version}
+ pom
+ import
+
+
+
+ org.mockito
+ mockito-bom
+ ${mockito.version}
+ pom
+ import
org.slf4j
- slf4j-api
+ slf4j-bom
${slf4j.version}
+ pom
+ import
+
+
+
+ biz.aQute.bnd
+ biz.aQute.bnd.annotation
+ ${bnd.annotation.version}
+ provided
+
+
+
+ org.jspecify
+ jspecify
+ ${jspecify.version}
+
+
+
+ org.osgi
+ org.osgi.annotation.bundle
+ ${osgi.bundle.version}
+ provided
+
+
+
+ org.osgi
+ org.osgi.annotation.versioning
+ ${osgi.versioning.version}
+
+
+
diff --git a/pom.xml b/pom.xml
index 02bc526..36841e0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,6 +59,7 @@
parent
+ log4j-api-to-slf4j
@@ -97,7 +98,15 @@
-
+
+
+
+ org.apache.logging.log4j
+ log4j-api-to-slf4j
+ ${project.version}
+
+
+
diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml
new file mode 100644
index 0000000..b0c83f0
--- /dev/null
+++ b/spotbugs-exclude.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+