You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by pk...@apache.org on 2023/01/28 11:52:03 UTC

[logging-log4j2] 01/03: [LOG4J2-3647] Add global filter support to `LogBuilder`

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

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

commit 6741782d7a87a5dd45895070b72d02ca2878e076
Author: Piotr P. Karwasz <pi...@karwasz.org>
AuthorDate: Fri Jan 13 06:15:27 2023 +0100

    [LOG4J2-3647] Add global filter support to `LogBuilder`
    
    `LogBuilder` suppliers in the `Logger` class always return a no-op
    builder if the log message level is higher than the configured level.
    This way global filters are always skipped.
    
    This patch always returns a functional builder if we detect the presence
    of a global filter.
---
 .../logging/log4j/internal/DefaultLogBuilder.java  | 138 +++++++++++++++++----
 .../apache/logging/log4j/spi/AbstractLogger.java   |  14 ++-
 .../logging/log4j/core/test/LogBuilderTest.java    |  88 +++++++++++++
 .../logging/log4j/core/test/LogBuilderTest.xml     |  29 +++++
 .../java/org/apache/logging/log4j/core/Logger.java |  15 +++
 .../perf/jmh/LogBuilderMarkerFilterBenchmark.java  | 135 ++++++++++++++++++++
 .../main/resources/log4j2-markerFilter-perf2.xml   |  31 +++++
 .../src/main/resources/log4j2-noFilter-perf.xml    |  30 +++++
 log4j-to-slf4j/pom.xml                             |  10 ++
 .../org/apache/logging/slf4j/SLF4JLogBuilder.java  | 128 +++++++++++++++++++
 .../java/org/apache/logging/slf4j/SLF4JLogger.java |  56 +++++++++
 .../org/apache/logging/slf4j/LogBuilderTest.java   | 106 ++++++++++++++++
 .../src/test/resources/logback-slf4j.xml           |  16 +++
 .../src/test/resources/logback-turbofilter.xml     |  39 ++++++
 14 files changed, 804 insertions(+), 31 deletions(-)

diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java
index 6a7a2f31cc..179f8b0d51 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java
@@ -16,6 +16,8 @@
  */
 package org.apache.logging.log4j.internal;
 
+import java.util.Arrays;
+
 import org.apache.logging.log4j.BridgeAware;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogBuilder;
@@ -23,13 +25,13 @@ import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.spi.ExtendedLogger;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.LambdaUtil;
 import org.apache.logging.log4j.util.StackLocatorUtil;
 import org.apache.logging.log4j.util.Strings;
 import org.apache.logging.log4j.util.Supplier;
 
-
 /**
  * Collects data for a log event and then logs it. This class should be considered private.
  */
@@ -39,7 +41,7 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder {
     private static final Logger LOGGER = StatusLogger.getLogger();
     private static final Message EMPTY_MESSAGE = new SimpleMessage(Strings.EMPTY);
 
-    private Logger logger;
+    private ExtendedLogger logger;
     private Level level;
     private Marker marker;
     private Throwable throwable;
@@ -48,7 +50,7 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder {
     private final long threadId;
     private String fqcn = FQCN;
 
-    public DefaultLogBuilder(final Logger logger, final Level level) {
+    public DefaultLogBuilder(ExtendedLogger logger, Level level) {
         this.logger = logger;
         this.level = level;
         this.threadId = Thread.currentThread().getId();
@@ -69,7 +71,7 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder {
      * @param level The logging level for this event.
      * @return This LogBuilder instance.
      */
-    public LogBuilder reset(Logger logger, Level level) {
+    public LogBuilder reset(ExtendedLogger logger, Level level) {
         this.logger = logger;
         this.level = level;
         this.marker = null;
@@ -105,7 +107,7 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder {
 
     @Override
     public void log(final Message message) {
-        if (isValid()) {
+        if (isValid() && isEnabled(message)) {
             logMessage(message);
         }
     }
@@ -113,99 +115,98 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder {
     @Override
     public Message logAndGet(Supplier<Message> messageSupplier) {
         Message message = null;
-        if (isValid()) {
-            logMessage(message = messageSupplier.get());
+        if (isValid() && isEnabled(message = messageSupplier.get())) {
+            logMessage(message);
         }
         return message;
     }
 
     @Override
     public void log(final CharSequence message) {
-        if (isValid()) {
+        if (isValid() && isEnabled(message)) {
             logMessage(logger.getMessageFactory().newMessage(message));
         }
     }
 
     @Override
     public void log(final String message) {
-        if (isValid()) {
+        if (isValid() && isEnabled(message)) {
             logMessage(logger.getMessageFactory().newMessage(message));
         }
     }
 
     @Override
     public void log(final String message, final Object... params) {
-        if (isValid()) {
+        if (isValid() && isEnabled(message, params)) {
             logMessage(logger.getMessageFactory().newMessage(message, params));
         }
     }
 
     @Override
     public void log(final String message, final Supplier<?>... params) {
-        if (isValid()) {
-            logMessage(logger.getMessageFactory().newMessage(message, LambdaUtil.getAll(params)));
+        final Object[] objs;
+        if (isValid() && isEnabled(message, objs = LambdaUtil.getAll(params))) {
+            logMessage(logger.getMessageFactory().newMessage(message, objs));
         }
     }
 
     @Override
     public void log(final Supplier<Message> messageSupplier) {
-        if (isValid()) {
-            logMessage(messageSupplier.get());
-        }
+        logAndGet(messageSupplier);
     }
 
     @Override
     public void log(final Object message) {
-        if (isValid()) {
+        if (isValid() && isEnabled(message)) {
             logMessage(logger.getMessageFactory().newMessage(message));
         }
     }
 
     @Override
     public void log(final String message, final Object p0) {
-        if (isValid()) {
+        if (isValid() && isEnabled(message, p0)) {
             logMessage(logger.getMessageFactory().newMessage(message, p0));
         }
     }
 
     @Override
     public void log(final String message, final Object p0, final Object p1) {
-        if (isValid()) {
+        if (isValid() && isEnabled(message, p0, p1)) {
             logMessage(logger.getMessageFactory().newMessage(message, p0, p1));
         }
     }
 
     @Override
     public void log(final String message, final Object p0, final Object p1, final Object p2) {
-        if (isValid()) {
+        if (isValid() && isEnabled(message, p0, p1, p2)) {
             logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2));
         }
     }
 
     @Override
     public void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3) {
-        if (isValid()) {
+        if (isValid() && isEnabled(message, p0, p1, p2, p3)) {
             logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3));
         }
     }
 
     @Override
     public void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) {
-        if (isValid()) {
+        if (isValid() && isEnabled(message, p0, p1, p2, p3, p4)) {
             logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4));
         }
     }
 
     @Override
     public void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5) {
-        if (isValid()) {
+        if (isValid() && isEnabled(message, p0, p1, p2, p3, p4, p5)) {
             logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5));
         }
     }
 
     @Override
     public void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6) {
-        if (isValid()) {
+        if (isValid() && isEnabled(message, p0, p1, p2, p3, p4, p5, p6)) {
             logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6));
         }
     }
@@ -213,7 +214,7 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder {
     @Override
     public void log(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) {
-        if (isValid()) {
+        if (isValid() && isEnabled(message, p0, p1, p2, p3, p4, p5, p6, p7)) {
             logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7));
         }
     }
@@ -221,7 +222,7 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder {
     @Override
     public void log(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) {
-        if (isValid()) {
+        if (isValid() && isEnabled(message, p0, p1, p2, p3, p4, p5, p6, p7, p8)) {
             logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8));
         }
     }
@@ -229,14 +230,14 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder {
     @Override
     public void log(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) {
-        if (isValid()) {
+        if (isValid() && isEnabled(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)) {
             logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9));
         }
     }
 
     @Override
     public void log() {
-        if (isValid()) {
+        if (isValid() && isEnabled(EMPTY_MESSAGE)) {
             logMessage(EMPTY_MESSAGE);
         }
     }
@@ -262,4 +263,87 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder {
         }
         return true;
     }
+
+    protected boolean isEnabled(Message message) {
+        return logger.isEnabled(level, marker, message, throwable);
+    }
+
+    protected boolean isEnabled(CharSequence message) {
+        return logger.isEnabled(level, marker, message, throwable);
+    }
+
+    protected boolean isEnabled(String message) {
+        return logger.isEnabled(level, marker, message, throwable);
+    }
+
+    protected boolean isEnabled(String message, Object... params) {
+        final Object[] newParams;
+        if (throwable != null) {
+            newParams = Arrays.copyOf(params, params.length + 1);
+            newParams[params.length] = throwable;
+        } else {
+            newParams = params;
+        }
+        return logger.isEnabled(level, marker, message, newParams);
+    }
+
+    protected boolean isEnabled(Object message) {
+        return logger.isEnabled(level, marker, message, throwable);
+    }
+
+    protected boolean isEnabled(String message, Object p0) {
+        return throwable != null ? logger.isEnabled(level, marker, message, p0, throwable)
+                : logger.isEnabled(level, marker, message, p0);
+    }
+
+    protected boolean isEnabled(String message, Object p0, Object p1) {
+        return throwable != null ? logger.isEnabled(level, marker, message, p0, p1, throwable)
+                : logger.isEnabled(level, marker, message, p0, p1);
+    }
+
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2) {
+        return throwable != null ? logger.isEnabled(level, marker, message, p0, p1, p2, throwable)
+                : logger.isEnabled(level, marker, message, p0, p1, p2);
+    }
+
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3) {
+        return throwable != null ? logger.isEnabled(level, marker, message, p0, p1, p2, p3, throwable)
+                : logger.isEnabled(level, marker, message, p0, p1, p2, p3);
+    }
+
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4) {
+        return throwable != null ? logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, throwable)
+                : logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4);
+    }
+
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5) {
+        return throwable != null ? logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, throwable)
+                : logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5);
+    }
+
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5,
+            Object p6) {
+        return throwable != null ? logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, throwable)
+                : logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6);
+    }
+
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5,
+            Object p6, Object p7) {
+        return throwable != null ? logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, throwable)
+                : logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7);
+    }
+
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5,
+            Object p6, Object p7, Object p8) {
+        return throwable != null
+                ? logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, throwable)
+                : logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8);
+    }
+
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5,
+            Object p6, Object p7, Object p8, Object p9) {
+        return throwable != null
+                ? logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, throwable)
+                : logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
+    }
 }
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
index dc76835569..e83c17dd4f 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
@@ -2751,13 +2751,19 @@ public abstract class AbstractLogger implements ExtendedLogger {
     @Override
     public LogBuilder atLevel(final Level level) {
         if (isEnabled(level)) {
-            return getLogBuilder(level).reset(this, level);
+            return getLogBuilder(level);
         }
         return LogBuilder.NOOP;
     }
 
-    private DefaultLogBuilder getLogBuilder(final Level level) {
-        final DefaultLogBuilder builder = logBuilder.get();
-        return Constants.isThreadLocalsEnabled() && !builder.isInUse() ? builder : new DefaultLogBuilder(this, level);
+    /**
+     * Returns a log builder that logs at the specified level.
+     *
+     * @since 2.20.0
+     */
+    protected LogBuilder getLogBuilder(Level level) {
+        DefaultLogBuilder builder = logBuilder.get();
+        return Constants.ENABLE_THREADLOCALS && !builder.isInUse() ? builder.reset(this, level)
+                : new DefaultLogBuilder(this, level);
     }
 }
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/LogBuilderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/LogBuilderTest.java
new file mode 100644
index 0000000000..7dc6b9191a
--- /dev/null
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/LogBuilderTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.test;
+
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import org.apache.logging.log4j.LogBuilder;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.MarkerManager;
+import org.apache.logging.log4j.core.LoggerContext;
+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.message.Message;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@LoggerContextSource("org/apache/logging/log4j/core/test/LogBuilderTest.xml")
+public class LogBuilderTest {
+
+    private static final Marker MARKER = MarkerManager.getMarker("TestMarker");
+    private static final CharSequence CHAR_SEQUENCE = "CharSequence";
+    private static final String STRING = "String";
+    private static final Message MESSAGE = new SimpleMessage();
+    private static final Throwable THROWABLE = new RuntimeException();
+    private static final Object[] P = {
+            "p0", "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9"
+    };
+    private static final Object OBJECT = "Object";
+
+    private Logger logger;
+    private ListAppender appender;
+
+    @BeforeEach
+    public void setupAppender(LoggerContext context, @Named("LIST") ListAppender appender) {
+        this.logger = context.getLogger(getClass());
+        this.appender = appender;
+    }
+
+    static Stream<Consumer<LogBuilder>> testMarkerFilter() {
+        return Stream.of(logBuilder -> logBuilder.log(),
+                logBuilder -> logBuilder.log(CHAR_SEQUENCE),
+                logBuilder -> logBuilder.log(MESSAGE),
+                logBuilder -> logBuilder.log(OBJECT),
+                logBuilder -> logBuilder.log(STRING),
+                logBuilder -> logBuilder.log(STRING, P[0]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6], P[7]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6], P[7], P[8]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6], P[7], P[8], P[9]),
+                logBuilder -> logBuilder.log(STRING, P), logBuilder -> logBuilder.log(STRING, () -> OBJECT),
+                logBuilder -> logBuilder.log(() -> MESSAGE));
+    }
+
+    @ParameterizedTest
+    @MethodSource
+    void testMarkerFilter(Consumer<LogBuilder> consumer) {
+        appender.clear();
+        consumer.accept(logger.atTrace().withMarker(MARKER));
+        consumer.accept(logger.atTrace().withThrowable(THROWABLE).withMarker(MARKER));
+        assertThat(appender.getEvents()).hasSize(2);
+    }
+}
diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/test/LogBuilderTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/test/LogBuilderTest.xml
new file mode 100644
index 0000000000..a71e8457eb
--- /dev/null
+++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/test/LogBuilderTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<Configuration name="LogBuilderTest" status="error">
+  <MarkerFilter marker="TestMarker" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
+  <Appenders>
+    <List name="LIST"/>
+  </Appenders>
+  <Loggers>
+    <Root level="INFO">
+      <AppenderRef ref="LIST"/>
+    </Root>
+  </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java
index f2dfacfc91..9da3d45454 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java
@@ -23,6 +23,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogBuilder;
 import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.LoggerConfig;
@@ -345,6 +346,16 @@ public class Logger extends AbstractLogger implements Supplier<LoggerConfig> {
         privateConfig.config.setLoggerAdditive(this, additive);
     }
 
+    @Override
+    public LogBuilder atLevel(Level level) {
+        // A global filter might accept messages less specific than level.
+        // Therefore we return always a functional builder.
+        if (privateConfig.hasFilter()) {
+            return getLogBuilder(level);
+        }
+        return super.atLevel(level);
+    }
+
     /**
      * Associates this Logger with a new Configuration. This method is not
      * exposed through the public API.
@@ -412,6 +423,10 @@ public class Logger extends AbstractLogger implements Supplier<LoggerConfig> {
             loggerConfig.log(event);
         }
 
+        boolean hasFilter() {
+            return config.getFilter() != null;
+        }
+
         boolean filter(final Level level, final Marker marker, final String msg) {
             final Filter filter = config.getFilter();
             if (filter != null) {
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/LogBuilderMarkerFilterBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/LogBuilderMarkerFilterBenchmark.java
new file mode 100644
index 0000000000..d22762fe32
--- /dev/null
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/LogBuilderMarkerFilterBenchmark.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.perf.jmh;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.LogBuilder;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.MarkerManager;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+
+/**
+ * <p>
+ * Compares {@link Logger} and {@link LogBuilder} in the presence or absence of
+ * a global filter. In the absence of a global filter ({@code -Dnofilter}) the
+ * {@link Logger} can return a no-op {@link LogBuilder} if the level is
+ * disabled. No such an optimization is possible in the presence of a global
+ * filter.
+ * </p>
+ * <p>
+ * HOW TO RUN THIS TEST
+ * </p>
+ * <ul>
+ * <li>single thread:
+ *
+ * <pre>
+ * java -jar target/benchmarks.jar ".*LogBuilderMarkerFilterBenchmark.*" -p useFilter=true,false
+ * </pre>
+ *
+ * </li>
+ * <li>multiple threads (for example, 4 threads):
+ *
+ * <pre>
+ * java -jar target/benchmarks.jar ".*LogBuilderMarkerFilterBenchmark.*" -p useFilter=true,false -t 4
+ * </pre>
+ *
+ * </li>
+ * </ul>
+ */
+@State(Scope.Benchmark)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.SECONDS)
+public class LogBuilderMarkerFilterBenchmark {
+
+    @Param("target/logbuilder.benchmark.log")
+    private String fileName;
+
+    @Param("true")
+    private boolean useFilter;
+
+    private static final String MESSAGE = "This is a test!";
+    private Marker marker;
+    private Logger logger;
+
+    @Setup
+    public void setUp() {
+        System.setProperty("logbuilder.benchmark.output", fileName);
+        if (useFilter) {
+            System.setProperty("log4j2.configurationFile", "log4j2-markerFilter-perf2.xml");
+        } else {
+            System.setProperty("log4j2.configurationFile", "log4j2-noFilter-perf.xml");
+        }
+        logger = LogManager.getLogger(getClass());
+        marker = MarkerManager.getMarker("TestMarker");
+    }
+
+    @TearDown
+    public void tearDown() throws IOException {
+        System.clearProperty("log4j2.configurationFile");
+        LogManager.shutdown();
+        Path filePath = Paths.get(fileName);
+        if (Files.isRegularFile(filePath)) {
+            Files.deleteIfExists(filePath);
+        }
+    }
+
+    @Benchmark
+    public void loggerTraceMarker() {
+        logger.trace(marker, MESSAGE);
+    }
+
+    @Benchmark
+    public void loggerInfoNoMarker() {
+        logger.info(MESSAGE);
+    }
+
+    @Benchmark
+    public void loggerTraceNoMarker() {
+        logger.trace(MESSAGE);
+    }
+
+    @Benchmark
+    public void logBuilderTraceMarker() {
+        logger.atTrace().withMarker(marker).log(MESSAGE);
+    }
+
+    @Benchmark
+    public void logBuilderInfoNoMarker() {
+        logger.atInfo().log(MESSAGE);
+    }
+
+    @Benchmark
+    public void logBuilderTraceNoMarker() {
+        logger.atTrace().log(MESSAGE);
+    }
+}
diff --git a/log4j-perf/src/main/resources/log4j2-markerFilter-perf2.xml b/log4j-perf/src/main/resources/log4j2-markerFilter-perf2.xml
new file mode 100644
index 0000000000..65858c4708
--- /dev/null
+++ b/log4j-perf/src/main/resources/log4j2-markerFilter-perf2.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<Configuration name="LogBuilderMarkerFilterBenchmark" status="error">
+  <MarkerFilter marker="TestMarker" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
+  <Appenders>
+    <File name="FILE" fileName="${sys:logbuilder.benchmark.output}">
+      <PatternLayout pattern="%m%n"/>
+    </File>
+  </Appenders>
+  <Loggers>
+    <Root level="INFO">
+      <AppenderRef ref="FILE"/>
+    </Root>
+  </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/log4j-perf/src/main/resources/log4j2-noFilter-perf.xml b/log4j-perf/src/main/resources/log4j2-noFilter-perf.xml
new file mode 100644
index 0000000000..c28d0facbe
--- /dev/null
+++ b/log4j-perf/src/main/resources/log4j2-noFilter-perf.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<Configuration name="LogBuilderMarkerFilterBenchmark" status="error">
+  <Appenders>
+    <File name="FILE" fileName="${sys:logbuilder.benchmark.output}">
+      <PatternLayout pattern="%m%n"/>
+    </File>
+  </Appenders>
+  <Loggers>
+    <Root level="INFO">
+      <AppenderRef ref="FILE"/>
+    </Root>
+  </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/log4j-to-slf4j/pom.xml b/log4j-to-slf4j/pom.xml
index 85d1f9c876..c6ae15d5b7 100644
--- a/log4j-to-slf4j/pom.xml
+++ b/log4j-to-slf4j/pom.xml
@@ -41,6 +41,11 @@
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.hamcrest</groupId>
       <artifactId>hamcrest</artifactId>
@@ -51,6 +56,11 @@
       <artifactId>junit-jupiter-engine</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-params</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.junit.vintage</groupId>
       <artifactId>junit-vintage-engine</artifactId>
diff --git a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogBuilder.java b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogBuilder.java
new file mode 100644
index 0000000000..ad978fb4b9
--- /dev/null
+++ b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogBuilder.java
@@ -0,0 +1,128 @@
+/*
+ * 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.Level;
+import org.apache.logging.log4j.internal.DefaultLogBuilder;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+
+public class SLF4JLogBuilder extends DefaultLogBuilder {
+
+    public SLF4JLogBuilder(ExtendedLogger logger, Level level) {
+        super(logger, level);
+    }
+
+    public SLF4JLogBuilder() {
+        super();
+    }
+
+    @Override
+    protected boolean isEnabled(Message message) {
+        // SLF4J will check again later
+        return true;
+    }
+
+    @Override
+    protected boolean isEnabled(CharSequence message) {
+        // SLF4J will check again later
+        return true;
+    }
+
+    @Override
+    protected boolean isEnabled(String message) {
+        // SLF4J will check again later
+        return true;
+    }
+
+    @Override
+    protected boolean isEnabled(String message, Object... params) {
+        // SLF4J will check again later
+        return true;
+    }
+
+    @Override
+    protected boolean isEnabled(Object message) {
+        // SLF4J will check again later
+        return true;
+    }
+
+    @Override
+    protected boolean isEnabled(String message, Object p0) {
+        // SLF4J will check again later
+        return true;
+    }
+
+    @Override
+    protected boolean isEnabled(String message, Object p0, Object p1) {
+        // SLF4J will check again later
+        return true;
+    }
+
+    @Override
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2) {
+        // SLF4J will check again later
+        return true;
+    }
+
+    @Override
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3) {
+        // SLF4J will check again later
+        return true;
+    }
+
+    @Override
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4) {
+        // SLF4J will check again later
+        return true;
+    }
+
+    @Override
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5) {
+        // SLF4J will check again later
+        return true;
+    }
+
+    @Override
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5,
+            Object p6) {
+        // SLF4J will check again later
+        return true;
+    }
+
+    @Override
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5,
+            Object p6, Object p7) {
+        // SLF4J will check again later
+        return true;
+    }
+
+    @Override
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5,
+            Object p6, Object p7, Object p8) {
+        // SLF4J will check again later
+        return true;
+    }
+
+    @Override
+    protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5,
+            Object p6, Object p7, Object p8, Object p9) {
+        // SLF4J will check again later
+        return true;
+    }
+
+}
diff --git a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java
index 0420f72578..fd8bbde404 100644
--- a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java
+++ b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java
@@ -17,11 +17,14 @@
 package org.apache.logging.slf4j;
 
 import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogBuilder;
 import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.message.LoggerNameAwareMessage;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.MessageFactory;
 import org.apache.logging.log4j.spi.AbstractLogger;
+import org.apache.logging.log4j.util.Constants;
+import org.slf4j.LoggerFactory;
 import org.slf4j.MarkerFactory;
 import org.slf4j.spi.LocationAwareLogger;
 
@@ -31,6 +34,14 @@ import org.slf4j.spi.LocationAwareLogger;
 public class SLF4JLogger extends AbstractLogger {
 
     private static final long serialVersionUID = 1L;
+    /**
+     * Logback supports turbo filters, that can override the logger's level.
+     * Therefore we can never return a no-op builder.
+     */
+    private static final boolean LAZY_LEVEL_CHECK = "ch.qos.logback.classic.LoggerContext"
+            .equals(LoggerFactory.getILoggerFactory().getClass().getName());
+    private static final ThreadLocal<SLF4JLogBuilder> logBuilder = ThreadLocal.withInitial(SLF4JLogBuilder::new);
+
     private final org.slf4j.Logger logger;
     private final LocationAwareLogger locationAwareLogger;
 
@@ -259,4 +270,49 @@ public class SLF4JLogger extends AbstractLogger {
         }
     }
 
+    @Override
+    public LogBuilder atTrace() {
+        return atLevel(Level.TRACE);
+    }
+
+    @Override
+    public LogBuilder atDebug() {
+        return atLevel(Level.DEBUG);
+    }
+
+    @Override
+    public LogBuilder atInfo() {
+        return atLevel(Level.INFO);
+    }
+
+    @Override
+    public LogBuilder atWarn() {
+        return atLevel(Level.WARN);
+    }
+
+    @Override
+    public LogBuilder atError() {
+        return atLevel(Level.ERROR);
+    }
+
+    @Override
+    public LogBuilder atFatal() {
+        return atLevel(Level.TRACE);
+    }
+
+    @Override
+    protected LogBuilder getLogBuilder(Level level) {
+        SLF4JLogBuilder builder = logBuilder.get();
+        return Constants.ENABLE_THREADLOCALS && !builder.isInUse() ? builder.reset(this, level)
+                : new SLF4JLogBuilder(this, level);
+    }
+
+    @Override
+    public LogBuilder atLevel(Level level) {
+        // TODO: wrap SLF4J 2.x LoggingEventBuilder
+        if (LAZY_LEVEL_CHECK) {
+            return getLogBuilder(level);
+        }
+        return super.atLevel(level);
+    }
 }
diff --git a/log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/LogBuilderTest.java b/log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/LogBuilderTest.java
new file mode 100644
index 0000000000..ec5ebfac50
--- /dev/null
+++ b/log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/LogBuilderTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.function.Consumer;
+import java.util.stream.Stream;
+
+import org.apache.logging.log4j.CloseableThreadContext;
+import org.apache.logging.log4j.CloseableThreadContext.Instance;
+import org.apache.logging.log4j.LogBuilder;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.testUtil.StringListAppender;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LogBuilderTest {
+
+    private static final String CONFIG = "target/test-classes/logback-turbofilter.xml";
+    private static final CharSequence CHAR_SEQUENCE = "CharSequence";
+    private static final String STRING = "String";
+    private static final Message MESSAGE = new SimpleMessage();
+    private static final Object[] P = { "p0", "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9" };
+    private static final Object OBJECT = "Object";
+
+    private static LoggerContext context;
+    private static Logger logger;
+    private static StringListAppender<ILoggingEvent> list;
+
+    @BeforeAll
+    public static void setUp() throws Exception {
+        context = (LoggerContext) LoggerFactory.getILoggerFactory();
+        final JoranConfigurator configurator = new JoranConfigurator();
+        configurator.setContext(context);
+        configurator.doConfigure(CONFIG);
+
+        final org.slf4j.Logger slf4jLogger = context.getLogger(LogBuilderTest.class);
+        logger = LogManager.getLogger(LogBuilderTest.class);
+        assertThat(slf4jLogger).isSameAs(((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).isNotNull().extracting("strList").isNotNull();
+        list.strList.clear();
+    }
+
+    static Stream<Consumer<LogBuilder>> logBuilderMethods() {
+        return Stream.of(logBuilder -> logBuilder.log(), logBuilder -> logBuilder.log(CHAR_SEQUENCE),
+                logBuilder -> logBuilder.log(MESSAGE), logBuilder -> logBuilder.log(OBJECT),
+                logBuilder -> logBuilder.log(STRING), logBuilder -> logBuilder.log(STRING, P[0]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6], P[7]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6], P[7], P[8]),
+                logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6], P[7], P[8], P[9]),
+                logBuilder -> logBuilder.log(STRING, P), logBuilder -> logBuilder.log(STRING, () -> OBJECT),
+                logBuilder -> logBuilder.log(() -> MESSAGE));
+    }
+
+    @ParameterizedTest
+    @MethodSource("logBuilderMethods")
+    void testTurboFilter(Consumer<LogBuilder> consumer) {
+        consumer.accept(logger.atTrace());
+        try (Instance c = CloseableThreadContext.put("callerId", "Log4j2")) {
+            consumer.accept(logger.atTrace());
+            assertThat(list.strList).hasSize(1);
+        }
+        list.strList.clear();
+    }
+
+    @ParameterizedTest
+    @MethodSource("logBuilderMethods")
+    void testLevelThreshold(Consumer<LogBuilder> consumer) {
+        consumer.accept(logger.atInfo());
+        assertThat(list.strList).hasSize(1);
+        list.strList.clear();
+    }
+}
diff --git a/log4j-to-slf4j/src/test/resources/logback-slf4j.xml b/log4j-to-slf4j/src/test/resources/logback-slf4j.xml
index ad45676fa9..8ade96b693 100644
--- a/log4j-to-slf4j/src/test/resources/logback-slf4j.xml
+++ b/log4j-to-slf4j/src/test/resources/logback-slf4j.xml
@@ -1,4 +1,20 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements. See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache license, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License. You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the license for the specific language governing permissions and
+  limitations under the license.
+  -->
 <configuration>
 
   <appender name="LIST" class="ch.qos.logback.core.testUtil.StringListAppender">
diff --git a/log4j-to-slf4j/src/test/resources/logback-turbofilter.xml b/log4j-to-slf4j/src/test/resources/logback-turbofilter.xml
new file mode 100644
index 0000000000..7b63422038
--- /dev/null
+++ b/log4j-to-slf4j/src/test/resources/logback-turbofilter.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements. See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache license, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License. You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the license for the specific language governing permissions and
+  limitations under the license.
+  -->
+<configuration>
+
+  <turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter">
+    <OnHigherOrEqual>ACCEPT</OnHigherOrEqual>
+    <Key>callerId</Key>
+    <DefaultThreshold>INFO</DefaultThreshold>
+    <MDCValueLevelPair>
+      <value>Log4j2</value>
+      <level>TRACE</level>
+    </MDCValueLevelPair>
+  </turboFilter>
+
+  <appender name="LIST" class="ch.qos.logback.core.testUtil.StringListAppender">
+    <layout class="ch.qos.logback.classic.PatternLayout">
+      <Pattern>%msg</Pattern>
+    </layout>
+  </appender>
+
+  <root level="INFO">
+    <appender-ref ref="LIST" />
+  </root>
+</configuration>