it = original.iterator();
+ while (it.hasNext()) {
+ final Marker next = it.next();
+ if (visited.contains(next)) {
+ LOGGER.warn("Found a cycle in Marker [{}]. Cycle will be broken.", next.getName());
+ } else {
+ visited.add(next);
+ marker.addParents(convertMarker(next, visited));
+ }
+ }
+ }
+ return marker;
+ }
+
+ /**
+ * Returns true if the Marker exists.
+ * @param name The Marker name.
+ * @return {@code true} if the Marker exists, {@code false} otherwise.
+ */
+ @Override
+ public boolean exists(final String name) {
+ return markerMap.containsKey(name);
+ }
+
+ /**
+ * Log4j does not support detached Markers. This method always returns false.
+ * @param name The Marker name.
+ * @return {@code false}
+ */
+ @Override
+ public boolean detachMarker(final String name) {
+ return false;
+ }
+
+ /**
+ * Log4j does not support detached Markers for performance reasons. The returned Marker is attached.
+ * @param name The Marker name.
+ * @return The named Marker (unmodified).
+ */
+ @Override
+ public Marker getDetachedMarker(final String name) {
+ LOGGER.warn("Log4j does not support detached Markers. Returned Marker [{}] will be unchanged.", name);
+ return getMarker(name);
+ }
+}
diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/SLF4JLoggingException.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/SLF4JLoggingException.java
new file mode 100644
index 0000000..57a65e1
--- /dev/null
+++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/SLF4JLoggingException.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+/**
+ * Exception thrown when the SLF4J adapter encounters a problem.
+ *
+ */
+public class SLF4JLoggingException extends RuntimeException {
+
+ /**
+ * Generated serial version ID.
+ */
+ private static final long serialVersionUID = -1618650972455089998L;
+
+ public SLF4JLoggingException(final String msg) {
+ super(msg);
+ }
+
+ public SLF4JLoggingException(final String msg, final Exception ex) {
+ super(msg, ex);
+ }
+
+ public SLF4JLoggingException(final Exception ex) {
+ super(ex);
+ }
+}
diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/SLF4JServiceProvider.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/SLF4JServiceProvider.java
new file mode 100644
index 0000000..abfdcd1
--- /dev/null
+++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/SLF4JServiceProvider.java
@@ -0,0 +1,60 @@
+/*
+ * 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 aQute.bnd.annotation.Resolution;
+import aQute.bnd.annotation.spi.ServiceProvider;
+import org.slf4j.ILoggerFactory;
+import org.slf4j.IMarkerFactory;
+import org.slf4j.spi.MDCAdapter;
+
+@ServiceProvider(value = org.slf4j.spi.SLF4JServiceProvider.class, resolution = Resolution.MANDATORY)
+public class SLF4JServiceProvider implements org.slf4j.spi.SLF4JServiceProvider {
+
+ public static final String REQUESTED_API_VERSION = "2.0.99";
+
+ private ILoggerFactory loggerFactory;
+ private Log4jMarkerFactory markerFactory;
+ private MDCAdapter mdcAdapter;
+
+ @Override
+ public ILoggerFactory getLoggerFactory() {
+ return loggerFactory;
+ }
+
+ @Override
+ public IMarkerFactory getMarkerFactory() {
+ return markerFactory;
+ }
+
+ @Override
+ public MDCAdapter getMDCAdapter() {
+ return mdcAdapter;
+ }
+
+ @Override
+ public String getRequestedApiVersion() {
+ return REQUESTED_API_VERSION;
+ }
+
+ @Override
+ public void initialize() {
+ markerFactory = new Log4jMarkerFactory();
+ loggerFactory = new Log4jLoggerFactory(markerFactory);
+ mdcAdapter = new Log4jMDCAdapter();
+ }
+}
diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/message/ThrowableConsumingMessageFactory.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/message/ThrowableConsumingMessageFactory.java
new file mode 100644
index 0000000..cb55499
--- /dev/null
+++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/message/ThrowableConsumingMessageFactory.java
@@ -0,0 +1,187 @@
+/*
+ * 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.message;
+
+import java.util.Arrays;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.MessageFactory2;
+import org.apache.logging.log4j.message.ObjectMessage;
+import org.apache.logging.log4j.message.ParameterizedMessage;
+import org.apache.logging.log4j.message.SimpleMessage;
+
+/**
+ * A message factory that eagerly removes a trailing throwable argument.
+ *
+ * This factory implements the algorithm used by Logback 1.1.x or later to determine
+ * {@link Message#getThrowable()}: if the last argument is a {@link Throwable} and there are less arguments than
+ * the number of placeholders incremented by one, the last argument will be used as
+ * {@link Message#getThrowable()} and will not appear in the parameterized message.
+ *
+ *
+ * The usual Log4j semantic only looks for throwables once all the placeholders have been filled.
+ *
+ * @since 2.24.0
+ */
+public final class ThrowableConsumingMessageFactory implements MessageFactory2 {
+
+ private Message newParameterizedMessage(final Object throwable, final String pattern, final Object... args) {
+ return new ParameterizedMessage(pattern, args, (Throwable) throwable);
+ }
+
+ @Override
+ public Message newMessage(final Object message) {
+ return new ObjectMessage(message);
+ }
+
+ @Override
+ public Message newMessage(final String message) {
+ return new SimpleMessage(message);
+ }
+
+ @Override
+ public Message newMessage(final String message, final Object... params) {
+ if (params != null && params.length > 0) {
+ final Object lastArg = params[params.length - 1];
+ return lastArg instanceof Throwable
+ ? newParameterizedMessage(lastArg, message, Arrays.copyOf(params, params.length - 1))
+ : newParameterizedMessage(null, message, params);
+ }
+ return new SimpleMessage(message);
+ }
+
+ @Override
+ public Message newMessage(final CharSequence charSequence) {
+ return new SimpleMessage(charSequence);
+ }
+
+ @Override
+ public Message newMessage(final String message, final Object p0) {
+ return p0 instanceof Throwable
+ ? newParameterizedMessage(p0, message)
+ : newParameterizedMessage(null, message, p0);
+ }
+
+ @Override
+ public Message newMessage(final String message, final Object p0, final Object p1) {
+ return p1 instanceof Throwable
+ ? newParameterizedMessage(p1, message, p0)
+ : newParameterizedMessage(null, message, p0, p1);
+ }
+
+ @Override
+ public Message newMessage(final String message, final Object p0, final Object p1, final Object p2) {
+ return p2 instanceof Throwable
+ ? newParameterizedMessage(p2, message, p0, p1)
+ : newParameterizedMessage(null, message, p0, p1, p2);
+ }
+
+ @Override
+ public Message newMessage(
+ final String message, final Object p0, final Object p1, final Object p2, final Object p3) {
+ return p3 instanceof Throwable
+ ? newParameterizedMessage(p3, message, p0, p1, p2)
+ : newParameterizedMessage(null, message, p0, p1, p2, p3);
+ }
+
+ @Override
+ public Message newMessage(
+ final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) {
+ return p4 instanceof Throwable
+ ? newParameterizedMessage(p4, message, p0, p1, p2, p3)
+ : newParameterizedMessage(null, message, p0, p1, p2, p3, p4);
+ }
+
+ @Override
+ public Message newMessage(
+ final String message,
+ final Object p0,
+ final Object p1,
+ final Object p2,
+ final Object p3,
+ final Object p4,
+ final Object p5) {
+ return p5 instanceof Throwable
+ ? newParameterizedMessage(p5, message, p0, p1, p2, p3, p4)
+ : newParameterizedMessage(null, message, p0, p1, p2, p3, p4, p5);
+ }
+
+ @Override
+ public Message newMessage(
+ final String message,
+ final Object p0,
+ final Object p1,
+ final Object p2,
+ final Object p3,
+ final Object p4,
+ final Object p5,
+ final Object p6) {
+ return p6 instanceof Throwable
+ ? newParameterizedMessage(p6, message, p0, p1, p2, p3, p4, p5)
+ : newParameterizedMessage(null, message, p0, p1, p2, p3, p4, p5, p6);
+ }
+
+ @Override
+ public Message newMessage(
+ final String message,
+ final Object p0,
+ final Object p1,
+ final Object p2,
+ final Object p3,
+ final Object p4,
+ final Object p5,
+ final Object p6,
+ final Object p7) {
+ return p7 instanceof Throwable
+ ? newParameterizedMessage(p7, message, p0, p1, p2, p3, p4, p5, p6)
+ : newParameterizedMessage(null, message, p0, p1, p2, p3, p4, p5, p6, p7);
+ }
+
+ @Override
+ public Message newMessage(
+ final String message,
+ final Object p0,
+ final Object p1,
+ final Object p2,
+ final Object p3,
+ final Object p4,
+ final Object p5,
+ final Object p6,
+ final Object p7,
+ final Object p8) {
+ return p8 instanceof Throwable
+ ? newParameterizedMessage(p8, message, p0, p1, p2, p3, p4, p5, p6, p7)
+ : newParameterizedMessage(null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8);
+ }
+
+ @Override
+ public Message newMessage(
+ final String message,
+ final Object p0,
+ final Object p1,
+ final Object p2,
+ final Object p3,
+ final Object p4,
+ final Object p5,
+ final Object p6,
+ final Object p7,
+ final Object p8,
+ final Object p9) {
+ return p9 instanceof Throwable
+ ? newParameterizedMessage(p9, message, p0, p1, p2, p3, p4, p5, p6, p7, p8)
+ : newParameterizedMessage(null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
+ }
+}
diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/message/package-info.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/message/package-info.java
new file mode 100644
index 0000000..764c1cf
--- /dev/null
+++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/message/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("2.24.0")
+package org.apache.logging.slf4j.message;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/package-info.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/package-info.java
new file mode 100644
index 0000000..511a8c7
--- /dev/null
+++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+/**
+ * SLF4J support. Note that this does indeed share the same package namespace as the one found in log4j-to-slf4j;
+ * this is intentional. The two JARs should not be used at the same time! Thus, in an OSGi environment
+ * where split packages are not allowed, this error is prevented due to both JARs sharing an exported package name.
+ */
+@Export
+@Header(name = Constants.BUNDLE_ACTIVATIONPOLICY, value = Constants.ACTIVATION_LAZY)
+@Version("2.21.0")
+package org.apache.logging.slf4j;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.bundle.Header;
+import org.osgi.annotation.versioning.Version;
+import org.osgi.framework.Constants;
diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/other/pkg/LoggerContextAnchorTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/other/pkg/LoggerContextAnchorTest.java
new file mode 100644
index 0000000..a2b7c7f
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/other/pkg/LoggerContextAnchorTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.other.pkg;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.status.StatusData;
+import org.apache.logging.log4j.status.StatusListener;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.junit.jupiter.api.Test;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test LoggerContext lookups by verifying the anchor class representing calling code.
+ */
+public class LoggerContextAnchorTest {
+ private static final String PREFIX = "Log4jLoggerFactory.getContext() found anchor class ";
+
+ @Test
+ public void testLoggerFactoryLookupClass() {
+ final String fqcn = getAnchorFqcn(() -> LoggerFactory.getLogger(LoggerContextAnchorTest.class));
+ assertEquals(getClass().getName(), fqcn);
+ }
+
+ @Test
+ public void testLoggerFactoryLookupString() {
+ final String fqcn = getAnchorFqcn(() -> LoggerFactory.getLogger("custom.logger"));
+ assertEquals(getClass().getName(), fqcn);
+ }
+
+ @Test
+ public void testLoggerFactoryGetILoggerFactoryLookup() {
+ final String fqcn =
+ getAnchorFqcn(() -> LoggerFactory.getILoggerFactory().getLogger("custom.logger"));
+ assertEquals(getClass().getName(), fqcn);
+ }
+
+ private static String getAnchorFqcn(final Runnable runnable) {
+ final List results = new CopyOnWriteArrayList<>();
+ final StatusListener listener = new StatusListener() {
+ @Override
+ public void log(final StatusData data) {
+ final String formattedMessage = data.getMessage().getFormattedMessage();
+ if (formattedMessage.startsWith(PREFIX)) {
+ results.add(formattedMessage.substring(PREFIX.length()));
+ }
+ }
+
+ @Override
+ public Level getStatusLevel() {
+ return Level.TRACE;
+ }
+
+ @Override
+ public void close() {
+ // nop
+ }
+ };
+ final StatusLogger statusLogger = StatusLogger.getLogger();
+ statusLogger.registerListener(listener);
+ try {
+ runnable.run();
+ if (results.isEmpty()) {
+ throw new AssertionError("Failed to locate an anchor lookup status message");
+ }
+ if (results.size() > 1) {
+ throw new AssertionError("Found multiple anchor lines: " + results);
+ }
+ return results.get(0);
+ } finally {
+ statusLogger.removeListener(listener);
+ }
+ }
+}
diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/CallerInformationTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/CallerInformationTest.java
new file mode 100644
index 0000000..b0bc3e2
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/CallerInformationTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import org.apache.logging.log4j.core.test.appender.ListAppender;
+import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
+import org.apache.logging.log4j.core.test.junit.Named;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.spi.CallerBoundaryAware;
+import org.slf4j.spi.LoggingEventBuilder;
+
+@LoggerContextSource("log4j2-calling-class.xml")
+public class CallerInformationTest {
+
+ @Test
+ public void testClassLogger(@Named("Class") final ListAppender app) throws Exception {
+ app.clear();
+ final Logger logger = LoggerFactory.getLogger("ClassLogger");
+ logger.info("Ignored message contents.");
+ logger.warn("Verifying the caller class is still correct.");
+ logger.error("Hopefully nobody breaks me!");
+ logger.atInfo().log("Ignored message contents.");
+ logger.atWarn().log("Verifying the caller class is still correct.");
+ logger.atError().log("Hopefully nobody breaks me!");
+ final List messages = app.getMessages();
+ assertEquals(6, messages.size(), "Incorrect number of messages.");
+ for (final String message : messages) {
+ assertEquals(this.getClass().getName(), message, "Incorrect caller class name.");
+ }
+ }
+
+ @Test
+ public void testMethodLogger(@Named("Method") final ListAppender app) throws Exception {
+ app.clear();
+ final Logger logger = LoggerFactory.getLogger("MethodLogger");
+ logger.info("More messages.");
+ logger.warn("CATASTROPHE INCOMING!");
+ logger.error("ZOMBIES!!!");
+ logger.warn("brains~~~");
+ logger.info("Itchy. Tasty.");
+ logger.atInfo().log("More messages.");
+ logger.atWarn().log("CATASTROPHE INCOMING!");
+ logger.atError().log("ZOMBIES!!!");
+ logger.atWarn().log("brains~~~");
+ logger.atInfo().log("Itchy. Tasty.");
+ final List messages = app.getMessages();
+ assertEquals(10, messages.size(), "Incorrect number of messages.");
+ for (final String message : messages) {
+ assertEquals("testMethodLogger", message, "Incorrect caller method name.");
+ }
+ }
+
+ @Test
+ public void testFqcnLogger(@Named("Fqcn") final ListAppender app) throws Exception {
+ app.clear();
+ final Logger logger = LoggerFactory.getLogger("FqcnLogger");
+ LoggingEventBuilder loggingEventBuilder = logger.atInfo();
+ ((CallerBoundaryAware) loggingEventBuilder).setCallerBoundary("MyFqcn");
+ loggingEventBuilder.log("A message");
+ final List messages = app.getMessages();
+ assertEquals(1, messages.size(), "Incorrect number of messages.");
+ for (final String message : messages) {
+ assertEquals("MyFqcn", message, "Incorrect fqcn.");
+ }
+ }
+}
diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/CustomFlatMarker.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/CustomFlatMarker.java
new file mode 100644
index 0000000..3d82209
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/CustomFlatMarker.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.slf4j;
+
+import java.util.Iterator;
+import org.slf4j.Marker;
+
+/**
+ * Test Marker that may contain no reference/parent Markers.
+ * @see LOG4J2-793
+ */
+public class CustomFlatMarker implements Marker {
+ private static final long serialVersionUID = -4115520883240247266L;
+
+ private final String name;
+
+ public CustomFlatMarker(final String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void add(final Marker reference) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean remove(final Marker reference) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean hasChildren() {
+ return hasReferences();
+ }
+
+ @Override
+ public boolean hasReferences() {
+ return false;
+ }
+
+ @Override
+ public Iterator iterator() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean contains(final Marker other) {
+ return false;
+ }
+
+ @Override
+ public boolean contains(final String name) {
+ return false;
+ }
+}
diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4j1222Test.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4j1222Test.java
new file mode 100644
index 0000000..6b4e509
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4j1222Test.java
@@ -0,0 +1,56 @@
+/*
+ * 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.junit.jupiter.api.Assertions.assertInstanceOf;
+
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tests logging during shutdown.
+ */
+public class Log4j1222Test {
+
+ @Test
+ public void homepageRendersSuccessfully() {
+ System.setProperty("log4j.configurationFile", "log4j2-console.xml");
+ Runtime.getRuntime().addShutdownHook(new ShutdownHook());
+ }
+
+ private static class ShutdownHook extends Thread {
+
+ private static class Holder {
+ private static final Logger LOGGER = LoggerFactory.getLogger(Log4j1222Test.class);
+ }
+
+ @Override
+ public void run() {
+ super.run();
+ trigger();
+ }
+
+ private void trigger() {
+ Holder.LOGGER.info("Attempt to trigger");
+ assertInstanceOf(
+ Log4jLogger.class,
+ Holder.LOGGER,
+ "Logger is of type " + Holder.LOGGER.getClass().getName());
+ }
+ }
+}
diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4j2_1482_Slf4jTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4j2_1482_Slf4jTest.java
new file mode 100644
index 0000000..d8abaf6
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4j2_1482_Slf4jTest.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.slf4j;
+
+import org.apache.logging.log4j.core.test.layout.Log4j2_1482_Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tests https://issues.apache.org/jira/browse/LOG4J2-1482
+ */
+public class Log4j2_1482_Slf4jTest extends Log4j2_1482_Test {
+
+ @Override
+ protected void log(final int runNumber) {
+ if (runNumber == 2) {
+ // System.out.println("Set a breakpoint here.");
+ }
+ final Logger logger = LoggerFactory.getLogger("auditcsvfile");
+ final int val1 = 9, val2 = 11, val3 = 12;
+ logger.info("Info Message!", val1, val2, val3);
+ logger.info("Info Message!", val1, val2, val3);
+ logger.info("Info Message!", val1, val2, val3);
+ }
+}
diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jEventBuilderTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jEventBuilderTest.java
new file mode 100644
index 0000000..8441bb1
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jEventBuilderTest.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.slf4j;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.test.appender.ListAppender;
+import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
+import org.apache.logging.log4j.core.test.junit.Named;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@LoggerContextSource("log4j2-config.xml")
+public class Log4jEventBuilderTest {
+
+ private final Logger logger;
+ private final ListAppender appender;
+
+ public Log4jEventBuilderTest(@Named("List") final Appender appender) {
+ logger = LoggerFactory.getLogger("org.apache.test.Log4jEventBuilderTest");
+ this.appender = (ListAppender) appender;
+ }
+
+ @BeforeEach
+ public void setUp() {
+ appender.clear();
+ }
+
+ @Test
+ public void testKeyValuePairs() {
+ logger.atDebug().addKeyValue("testKeyValuePairs", "ok").log();
+ final List events = appender.getEvents();
+ assertThat(events).hasSize(1);
+ assertThat(events.get(0).getContextData().toMap()).containsEntry("testKeyValuePairs", "ok");
+ }
+
+ @Test
+ public void testArguments() {
+ logger.atDebug().setMessage("{}-{}").addArgument("a").addArgument("b").log();
+ logger.atDebug().log("{}-{}", "a", "b");
+ logger.atDebug().addArgument("a").log("{}-{}", "b");
+ logger.atDebug().log("{}-{}", new Object[] {"a", "b"});
+ assertThat(appender.getEvents()).hasSize(4).allMatch(event -> "a-b"
+ .equals(event.getMessage().getFormattedMessage()));
+ }
+}
diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jMDCAdapterTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jMDCAdapterTest.java
new file mode 100644
index 0000000..0559608
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jMDCAdapterTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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 java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class Log4jMDCAdapterTest {
+
+ private static final Log4jMDCAdapter MDC_ADAPTER = new Log4jMDCAdapter();
+ private static final String KEY = "Log4j2";
+
+ private static Deque createDeque(final int size) {
+ final Deque result = new ArrayDeque<>(size);
+ IntStream.range(0, size).mapToObj(Integer::toString).forEach(result::addLast);
+ return result;
+ }
+
+ private static Deque popDeque(final String key) {
+ final Deque result = new ArrayDeque<>();
+ String value;
+ while ((value = MDC_ADAPTER.popByKey(key)) != null) {
+ result.addLast(value);
+ }
+ return result;
+ }
+
+ static Stream keys() {
+ return Stream.of(KEY, "", null);
+ }
+
+ @ParameterizedTest
+ @MethodSource("keys")
+ public void testPushPopByKey(final String key) {
+ MDC_ADAPTER.clearDequeByKey(key);
+ final Deque expectedValues = createDeque(100);
+ expectedValues.descendingIterator().forEachRemaining(v -> MDC_ADAPTER.pushByKey(key, v));
+ assertThat(MDC_ADAPTER.getCopyOfDequeByKey(key)).containsExactlyElementsOf(expectedValues);
+ assertThat(popDeque(key)).containsExactlyElementsOf(expectedValues);
+ }
+}
diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jMarkerTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jMarkerTest.java
new file mode 100644
index 0000000..4926d3b
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jMarkerTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.MarkerManager;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class Log4jMarkerTest {
+
+ private static Log4jMarkerFactory markerFactory;
+
+ @BeforeAll
+ public static void startup() {
+ markerFactory = ((Log4jLoggerFactory) org.slf4j.LoggerFactory.getILoggerFactory()).getMarkerFactory();
+ }
+
+ @Test
+ public void testEquals() {
+ final Marker markerA = MarkerManager.getMarker(Log4jMarkerTest.class.getName() + "-A");
+ final Marker markerB = MarkerManager.getMarker(Log4jMarkerTest.class.getName() + "-B");
+ final Log4jMarker marker1 = new Log4jMarker(markerFactory, markerA);
+ final Log4jMarker marker2 = new Log4jMarker(markerFactory, markerA);
+ final Log4jMarker marker3 = new Log4jMarker(markerFactory, markerB);
+ assertEquals(marker1, marker2);
+ assertNotEquals(marker1, null);
+ assertNotEquals(null, marker1);
+ assertNotEquals(marker1, marker3);
+ }
+}
diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/LoggerContextTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/LoggerContextTest.java
new file mode 100644
index 0000000..720bcfc
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/LoggerContextTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Set;
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.spi.LoggerContext;
+import org.junit.jupiter.api.Test;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tests cleanup of the LoggerContexts.
+ */
+public class LoggerContextTest {
+
+ @Test
+ public void testCleanup() throws Exception {
+ final Log4jLoggerFactory factory = (Log4jLoggerFactory) LoggerFactory.getILoggerFactory();
+ factory.getLogger("test");
+ Set set = factory.getLoggerContexts();
+ final LoggerContext ctx1 = set.toArray(LoggerContext.EMPTY_ARRAY)[0];
+ assertInstanceOf(LifeCycle.class, ctx1, "LoggerContext is not enabled for shutdown");
+ ((LifeCycle) ctx1).stop();
+ set = factory.getLoggerContexts();
+ assertTrue(set.isEmpty(), "Expected no LoggerContexts");
+ }
+}
diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/LoggerTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/LoggerTest.java
new file mode 100644
index 0000000..86e6cb2
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/LoggerTest.java
@@ -0,0 +1,210 @@
+/*
+ * 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.List;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.core.test.appender.ListAppender;
+import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
+import org.apache.logging.log4j.core.test.junit.Named;
+import org.apache.logging.log4j.util.Strings;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+import org.slf4j.Marker;
+import org.slf4j.spi.LocationAwareLogger;
+import org.slf4j.spi.LoggingEventBuilder;
+
+/**
+ *
+ */
+@LoggerContextSource("log4j-test1.xml")
+public class LoggerTest {
+
+ private final Logger logger;
+ private final LoggerContext ctx;
+
+ @Test
+ public void debug() {
+ logger.debug("Debug message");
+ verify("o.a.l.s.LoggerTest Debug message MDC{}" + Strings.LINE_SEPARATOR);
+ }
+
+ public LoggerTest(final LoggerContext context) {
+ this.ctx = context;
+ this.logger = LoggerFactory.getLogger("LoggerTest");
+ }
+
+ @Test
+ public void debugNoParms() {
+ logger.debug("Debug message {}");
+ verify("o.a.l.s.LoggerTest Debug message {} MDC{}" + Strings.LINE_SEPARATOR);
+ logger.debug("Debug message {}", (Object[]) null);
+ verify("o.a.l.s.LoggerTest Debug message {} MDC{}" + Strings.LINE_SEPARATOR);
+ ((LocationAwareLogger) logger)
+ .log(null, Log4jLogger.class.getName(), LocationAwareLogger.DEBUG_INT, "Debug message {}", null, null);
+ verify("o.a.l.s.LoggerTest Debug message {} MDC{}" + Strings.LINE_SEPARATOR);
+ }
+
+ @Test
+ public void debugWithParms() {
+ logger.debug("Hello, {}", "World");
+ verify("o.a.l.s.LoggerTest Hello, World MDC{}" + Strings.LINE_SEPARATOR);
+ }
+
+ @Test
+ public void mdc() {
+
+ MDC.put("TestYear", "2010");
+ logger.debug("Debug message");
+ verify("o.a.l.s.LoggerTest Debug message MDC{TestYear=2010}" + Strings.LINE_SEPARATOR);
+ MDC.clear();
+ logger.debug("Debug message");
+ verify("o.a.l.s.LoggerTest Debug message MDC{}" + Strings.LINE_SEPARATOR);
+ }
+
+ @Test
+ public void mdcStack() {
+ MDC.pushByKey("TestYear", "2010");
+ logger.debug("Debug message");
+ verify("o.a.l.s.LoggerTest Debug message MDC{TestYear=2010}" + Strings.LINE_SEPARATOR);
+ MDC.pushByKey("TestYear", "2011");
+ logger.debug("Debug message");
+ verify("o.a.l.s.LoggerTest Debug message MDC{TestYear=2011}" + Strings.LINE_SEPARATOR);
+ MDC.popByKey("TestYear");
+ logger.debug("Debug message");
+ verify("o.a.l.s.LoggerTest Debug message MDC{TestYear=2010}" + Strings.LINE_SEPARATOR);
+ MDC.clear();
+ logger.debug("Debug message");
+ verify("o.a.l.s.LoggerTest Debug message MDC{}" + Strings.LINE_SEPARATOR);
+ }
+
+ /**
+ * @see LOG4J2-793
+ */
+ @Test
+ public void supportsCustomSLF4JMarkers() {
+ final Marker marker = new CustomFlatMarker("TEST");
+ logger.debug(marker, "Test");
+ verify("o.a.l.s.LoggerTest Test MDC{}" + Strings.LINE_SEPARATOR);
+ }
+
+ @Test
+ public void testRootLogger() {
+ final Logger l = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
+ assertNotNull(l, "No Root Logger");
+ assertEquals(Logger.ROOT_LOGGER_NAME, l.getName());
+ }
+
+ @Test
+ public void doubleSubst() {
+ logger.debug("Hello, {}", "Log4j {}");
+ verify("o.a.l.s.LoggerTest Hello, Log4j {} MDC{}" + Strings.LINE_SEPARATOR);
+ }
+
+ @Test
+ public void testThrowable() {
+ final Throwable expected = new RuntimeException();
+ logger.debug("Hello {}", expected);
+ verifyThrowable(expected);
+ logger.debug("Hello {}", (Object) expected);
+ verifyThrowable(null);
+ logger.debug("Hello", expected);
+ verifyThrowable(expected);
+ logger.debug("Hello {}! {}", "World!", expected);
+ verifyThrowable(null);
+ logger.debug("Hello {}!", "World!", expected);
+ verifyThrowable(expected);
+ final LocationAwareLogger lal = (LocationAwareLogger) logger;
+ lal.log(null, LoggerTest.class.getName(), LocationAwareLogger.DEBUG_INT, "Hello {}", null, expected);
+ verifyThrowable(expected);
+ lal.log(
+ null,
+ LoggerTest.class.getName(),
+ LocationAwareLogger.DEBUG_INT,
+ "Hello {}",
+ new Object[] {expected},
+ null);
+ verifyThrowable(null);
+ lal.log(
+ null,
+ LoggerTest.class.getName(),
+ LocationAwareLogger.DEBUG_INT,
+ "Hello {}",
+ new Object[] {"World!", expected},
+ null);
+ verifyThrowable(expected);
+ }
+
+ @Test
+ public void testLazyLoggingEventBuilder() {
+ final ListAppender appender = ctx.getConfiguration().getAppender("UnformattedList");
+ final Level oldLevel = ctx.getRootLogger().getLevel();
+ try {
+ Configurator.setRootLevel(Level.ERROR);
+ final LoggingEventBuilder builder = logger.makeLoggingEventBuilder(org.slf4j.event.Level.DEBUG);
+ Configurator.setRootLevel(Level.DEBUG);
+ builder.log();
+ assertThat(appender.getEvents()).hasSize(1).map(LogEvent::getLevel).containsExactly(Level.DEBUG);
+ } finally {
+ Configurator.setRootLevel(oldLevel);
+ }
+ }
+
+ private ListAppender getAppenderByName(final String name) {
+ final ListAppender listApp = ctx.getConfiguration().getAppender(name);
+ assertNotNull(listApp, "Missing Appender");
+ return listApp;
+ }
+
+ private void verify(final String expected) {
+ final ListAppender listApp = getAppenderByName("List");
+ final List events = listApp.getMessages();
+ assertEquals(1, events.size(), "Incorrect number of messages. Expected 1 Actual " + events.size());
+ final String actual = events.get(0);
+ assertEquals(expected, actual, "Incorrect message. Expected \" + expected + \". Actual \" + actual");
+ listApp.clear();
+ }
+
+ private void verifyThrowable(final Throwable expected) {
+ final ListAppender listApp = getAppenderByName("UnformattedList");
+ final List events = listApp.getEvents();
+ assertEquals(1, events.size(), "Incorrect number of messages");
+ final LogEvent actual = events.get(0);
+ assertEquals(expected, actual.getThrown(), "Incorrect throwable.");
+ listApp.clear();
+ }
+
+ @BeforeEach
+ @AfterEach
+ public void cleanup(
+ @Named("List") final ListAppender list, @Named("UnformattedList") final ListAppender unformattedList) {
+ MDC.clear();
+ list.clear();
+ unformattedList.clear();
+ }
+}
diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/MarkerTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/MarkerTest.java
new file mode 100644
index 0000000..c81045a
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/MarkerTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.MarkerManager;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ *
+ */
+public class MarkerTest {
+
+ private static final String CHILD_MAKER_NAME = MarkerTest.class.getSimpleName() + "-TEST";
+ private static final String PARENT_MARKER_NAME = MarkerTest.class.getSimpleName() + "-PARENT";
+ private static Log4jMarkerFactory markerFactory;
+
+ @BeforeAll
+ public static void startup() {
+ markerFactory = ((Log4jLoggerFactory) org.slf4j.LoggerFactory.getILoggerFactory()).getMarkerFactory();
+ }
+
+ @BeforeEach
+ @AfterEach
+ public void clearMarkers() {
+ MarkerManager.clear();
+ }
+
+ @Test
+ public void testAddMarker() {
+ final String childMakerName = CHILD_MAKER_NAME + "-AM";
+ final String parentMarkerName = PARENT_MARKER_NAME + "-AM";
+ final org.slf4j.Marker slf4jMarker = org.slf4j.MarkerFactory.getMarker(childMakerName);
+ final org.slf4j.Marker slf4jParent = org.slf4j.MarkerFactory.getMarker(parentMarkerName);
+ slf4jMarker.add(slf4jParent);
+ final Marker log4jParent = MarkerManager.getMarker(parentMarkerName);
+ final Marker log4jMarker = MarkerManager.getMarker(childMakerName);
+
+ assertInstanceOf(Log4jMarker.class, slf4jMarker, "Incorrect Marker class");
+ assertTrue(
+ log4jMarker.isInstanceOf(log4jParent),
+ String.format(
+ "%s (log4jMarker=%s) is not an instance of %s (log4jParent=%s) in Log4j",
+ childMakerName, parentMarkerName, log4jMarker, log4jParent));
+ assertTrue(
+ slf4jMarker.contains(slf4jParent),
+ String.format(
+ "%s (slf4jMarker=%s) is not an instance of %s (log4jParent=%s) in SLF4J",
+ childMakerName, parentMarkerName, slf4jMarker, slf4jParent));
+ }
+
+ @Test
+ public void testAddNullMarker() {
+ final String childMarkerName = CHILD_MAKER_NAME + "-ANM";
+ final String parentMakerName = PARENT_MARKER_NAME + "-ANM";
+ final org.slf4j.Marker slf4jMarker = org.slf4j.MarkerFactory.getMarker(childMarkerName);
+ final org.slf4j.Marker slf4jParent = org.slf4j.MarkerFactory.getMarker(parentMakerName);
+ slf4jMarker.add(slf4jParent);
+ final Marker log4jParent = MarkerManager.getMarker(parentMakerName);
+ final Marker log4jMarker = MarkerManager.getMarker(childMarkerName);
+ final Log4jMarker log4jSlf4jParent = new Log4jMarker(markerFactory, log4jParent);
+ final Log4jMarker log4jSlf4jMarker = new Log4jMarker(markerFactory, log4jMarker);
+ final org.slf4j.Marker nullMarker = null;
+ try {
+ log4jSlf4jParent.add(nullMarker);
+ fail("Expected " + IllegalArgumentException.class.getName());
+ } catch (final IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ log4jSlf4jMarker.add(nullMarker);
+ fail("Expected " + IllegalArgumentException.class.getName());
+ } catch (final IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testAddSameMarker() {
+ final String childMarkerName = CHILD_MAKER_NAME + "-ASM";
+ final String parentMakerName = PARENT_MARKER_NAME + "-ASM";
+ final org.slf4j.Marker slf4jMarker = org.slf4j.MarkerFactory.getMarker(childMarkerName);
+ final org.slf4j.Marker slf4jParent = org.slf4j.MarkerFactory.getMarker(parentMakerName);
+ slf4jMarker.add(slf4jParent);
+ slf4jMarker.add(slf4jParent);
+ final Marker log4jParent = MarkerManager.getMarker(parentMakerName);
+ final Marker log4jMarker = MarkerManager.getMarker(childMarkerName);
+ assertTrue(
+ log4jMarker.isInstanceOf(log4jParent),
+ String.format(
+ "%s (log4jMarker=%s) is not an instance of %s (log4jParent=%s) in Log4j",
+ childMarkerName, parentMakerName, log4jMarker, log4jParent));
+ assertTrue(
+ slf4jMarker.contains(slf4jParent),
+ String.format(
+ "%s (slf4jMarker=%s) is not an instance of %s (log4jParent=%s) in SLF4J",
+ childMarkerName, parentMakerName, slf4jMarker, slf4jParent));
+ }
+
+ @Test
+ public void testEquals() {
+ final String childMarkerName = CHILD_MAKER_NAME + "-ASM";
+ final String parentMakerName = PARENT_MARKER_NAME + "-ASM";
+ final org.slf4j.Marker slf4jMarker = org.slf4j.MarkerFactory.getMarker(childMarkerName);
+ final org.slf4j.Marker slf4jMarker2 = org.slf4j.MarkerFactory.getMarker(childMarkerName);
+ final org.slf4j.Marker slf4jParent = org.slf4j.MarkerFactory.getMarker(parentMakerName);
+ slf4jMarker.add(slf4jParent);
+ final Marker log4jParent = MarkerManager.getMarker(parentMakerName);
+ final Marker log4jMarker = MarkerManager.getMarker(childMarkerName);
+ final Marker log4jMarker2 = MarkerManager.getMarker(childMarkerName);
+ assertEquals(log4jMarker, log4jMarker2);
+ assertEquals(slf4jMarker, slf4jMarker2);
+ assertNotEquals(log4jParent, log4jMarker);
+ assertNotEquals(slf4jParent, slf4jMarker);
+ }
+
+ @Test
+ public void testContainsNullMarker() {
+ final String childMarkerName = CHILD_MAKER_NAME + "-CM";
+ final String parentMakerName = PARENT_MARKER_NAME + "-CM";
+ final org.slf4j.Marker slf4jMarker = org.slf4j.MarkerFactory.getMarker(childMarkerName);
+ final org.slf4j.Marker slf4jParent = org.slf4j.MarkerFactory.getMarker(parentMakerName);
+ slf4jMarker.add(slf4jParent);
+ final Marker log4jParent = MarkerManager.getMarker(parentMakerName);
+ final Marker log4jMarker = MarkerManager.getMarker(childMarkerName);
+ final Log4jMarker log4jSlf4jParent = new Log4jMarker(markerFactory, log4jParent);
+ final Log4jMarker log4jSlf4jMarker = new Log4jMarker(markerFactory, log4jMarker);
+ final org.slf4j.Marker nullMarker = null;
+ try {
+ assertFalse(log4jSlf4jParent.contains(nullMarker));
+ fail("Expected " + IllegalArgumentException.class.getName());
+ } catch (final IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ assertFalse(log4jSlf4jMarker.contains(nullMarker));
+ fail("Expected " + IllegalArgumentException.class.getName());
+ } catch (final IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testContainsNullString() {
+ final String childMarkerName = CHILD_MAKER_NAME + "-CS";
+ final String parentMakerName = PARENT_MARKER_NAME + "-CS";
+ final org.slf4j.Marker slf4jMarker = org.slf4j.MarkerFactory.getMarker(childMarkerName);
+ final org.slf4j.Marker slf4jParent = org.slf4j.MarkerFactory.getMarker(parentMakerName);
+ slf4jMarker.add(slf4jParent);
+ final Marker log4jParent = MarkerManager.getMarker(parentMakerName);
+ final Marker log4jMarker = MarkerManager.getMarker(childMarkerName);
+ final Log4jMarker log4jSlf4jParent = new Log4jMarker(markerFactory, log4jParent);
+ final Log4jMarker log4jSlf4jMarker = new Log4jMarker(markerFactory, log4jMarker);
+ final String nullStr = null;
+ assertFalse(log4jSlf4jParent.contains(nullStr));
+ assertFalse(log4jSlf4jMarker.contains(nullStr));
+ }
+
+ @Test
+ public void testRemoveNullMarker() {
+ final String childMakerName = CHILD_MAKER_NAME + "-CM";
+ final String parentMakerName = PARENT_MARKER_NAME + "-CM";
+ final org.slf4j.Marker slf4jMarker = org.slf4j.MarkerFactory.getMarker(childMakerName);
+ final org.slf4j.Marker slf4jParent = org.slf4j.MarkerFactory.getMarker(parentMakerName);
+ slf4jMarker.add(slf4jParent);
+ final Marker log4jParent = MarkerManager.getMarker(parentMakerName);
+ final Marker log4jMarker = MarkerManager.getMarker(childMakerName);
+ final Log4jMarker log4jSlf4jParent = new Log4jMarker(markerFactory, log4jParent);
+ final Log4jMarker log4jSlf4jMarker = new Log4jMarker(markerFactory, log4jMarker);
+ final org.slf4j.Marker nullMarker = null;
+ assertFalse(log4jSlf4jParent.remove(nullMarker));
+ assertFalse(log4jSlf4jMarker.remove(nullMarker));
+ }
+}
diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/OverflowTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/OverflowTest.java
new file mode 100644
index 0000000..d3c7871
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/OverflowTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.junit.jupiter.api.Assertions.fail;
+
+import org.apache.logging.log4j.LoggingException;
+import org.junit.jupiter.api.Test;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tests StackOverflow when slf4j-impl and to-slf4j are both present.
+ */
+public class OverflowTest {
+
+ @Test
+ public void log() {
+ try {
+ LoggerFactory.getLogger(OverflowTest.class);
+ fail("Failed to detect inclusion of log4j-to-slf4j");
+ } catch (LoggingException ex) {
+ // Expected exception.
+ } catch (StackOverflowError error) {
+ fail("Failed to detect inclusion of log4j-to-slf4j, caught StackOverflowError");
+ }
+ }
+}
diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/SerializeTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/SerializeTest.java
new file mode 100644
index 0000000..320e424
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/SerializeTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.apache.logging.log4j.test.SerializableMatchers.serializesRoundTrip;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.io.Serializable;
+import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ */
+@LoggerContextSource(value = "log4j-test1.xml")
+public class SerializeTest {
+
+ Logger logger = LoggerFactory.getLogger("LoggerTest");
+
+ @Test
+ public void testLogger() throws Exception {
+ assertThat((Serializable) logger, serializesRoundTrip());
+ }
+}
diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/message/ThrowableConsumingMessageFactoryTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/message/ThrowableConsumingMessageFactoryTest.java
new file mode 100644
index 0000000..2a111be
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/message/ThrowableConsumingMessageFactoryTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.message;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.MessageFactory2;
+import org.junit.jupiter.api.Test;
+
+public class ThrowableConsumingMessageFactoryTest {
+
+ private static final String MESSAGE = "MESSAGE";
+ private static final Object P0 = new Object();
+ private static final Object P1 = new Object();
+ private static final Object P2 = new Object();
+ private static final Object P3 = new Object();
+ private static final Object P4 = new Object();
+ private static final Object P5 = new Object();
+ private static final Object P6 = new Object();
+ private static final Object P7 = new Object();
+ private static final Object P8 = new Object();
+ private static final Object P9 = new Object();
+ private static final Object P10 = new Object();
+ private static final Object THROWABLE = new Throwable();
+
+ @Test
+ void should_not_consume_last_object_parameter() {
+ final MessageFactory2 factory = new ThrowableConsumingMessageFactory();
+ assertThat(factory.newMessage(MESSAGE, P0))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(1, null);
+ assertThat(factory.newMessage(MESSAGE, P0, P1))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(2, null);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(3, null);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(4, null);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(5, null);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(6, null);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(7, null);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, P7))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(8, null);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, P7, P8))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(9, null);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(10, null);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(11, null);
+ }
+
+ @Test
+ void should_consume_last_throwable_parameter() {
+ final MessageFactory2 factory = new ThrowableConsumingMessageFactory();
+ assertThat(factory.newMessage(MESSAGE, THROWABLE))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(0, THROWABLE);
+ assertThat(factory.newMessage(MESSAGE, P0, THROWABLE))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(1, THROWABLE);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, THROWABLE))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(2, THROWABLE);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, THROWABLE))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(3, THROWABLE);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, THROWABLE))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(4, THROWABLE);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, THROWABLE))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(5, THROWABLE);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, THROWABLE))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(6, THROWABLE);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, THROWABLE))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(7, THROWABLE);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, P7, THROWABLE))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(8, THROWABLE);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, P7, P8, THROWABLE))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(9, THROWABLE);
+ assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, THROWABLE))
+ .extracting(m -> m.getParameters().length, Message::getThrowable)
+ .as("checking parameter count and throwable")
+ .containsExactly(10, THROWABLE);
+ }
+}
diff --git a/slf4j-to-log4j-api/src/test/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider b/slf4j-to-log4j-api/src/test/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider
new file mode 100644
index 0000000..5552b70
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider
@@ -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 file, but we need it sooner in our tests
+org.apache.logging.slf4j.SLF4JServiceProvider
\ No newline at end of file
diff --git a/slf4j-to-log4j-api/src/test/resources/log4j-test1.xml b/slf4j-to-log4j-api/src/test/resources/log4j-test1.xml
new file mode 100644
index 0000000..30126e6
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/resources/log4j-test1.xml
@@ -0,0 +1,51 @@
+
+
+
+
+ target/test.log
+
+
+
+
+
+
+
+
+
+ %d %p %C{1.} [%t] %m%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/slf4j-to-log4j-api/src/test/resources/log4j2-1482.xml b/slf4j-to-log4j-api/src/test/resources/log4j2-1482.xml
new file mode 100644
index 0000000..7c5e728
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/resources/log4j2-1482.xml
@@ -0,0 +1,42 @@
+
+
+
+
+ target/log4j2-1482
+ audit
+ param1,param2,param3${sys:line.separator}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/slf4j-to-log4j-api/src/test/resources/log4j2-config.xml b/slf4j-to-log4j-api/src/test/resources/log4j2-config.xml
new file mode 100644
index 0000000..d247bd1
--- /dev/null
+++ b/slf4j-to-log4j-api/src/test/resources/log4j2-config.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+