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 549cc4f3c8a..b3d34d8163c 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,12 +25,12 @@ 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.Supplier; - /** * Collects data for a log event and then logs it. This class should be considered private. */ @@ -38,7 +40,7 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder { private static final String FQCN = DefaultLogBuilder.class.getName(); private static final Logger LOGGER = StatusLogger.getLogger(); - private Logger logger; + private ExtendedLogger logger; private Level level; private Marker marker; private Throwable throwable; @@ -47,7 +49,7 @@ public class DefaultLogBuilder implements BridgeAware, LogBuilder { private long threadId; private String fqcn = FQCN; - public DefaultLogBuilder(Logger logger, Level level) { + public DefaultLogBuilder(ExtendedLogger logger, Level level) { this.logger = logger; this.level = level; this.threadId = Thread.currentThread().getId(); @@ -68,7 +70,7 @@ public void setEntryPoint(String fqcn) { * @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; @@ -108,7 +110,7 @@ public boolean isInUse() { @Override public void log(Message message) { - if (isValid()) { + if (isValid() && isEnabled(message)) { logMessage(message); } } @@ -116,99 +118,98 @@ public void log(Message message) { @Override public Message logAndGet(Supplier 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(String message) { - if (isValid()) { + if (isValid() && isEnabled(message)) { logMessage(logger.getMessageFactory().newMessage(message)); } } @Override public void log(String message, Object... params) { - if (isValid()) { + if (isValid() && isEnabled(message, params)) { logMessage(logger.getMessageFactory().newMessage(message, params)); } } @Override public void log(String message, 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(Supplier messageSupplier) { - if (isValid()) { - logMessage(messageSupplier.get()); - } + logAndGet(messageSupplier); } @Override public void log(Object message) { - if (isValid()) { + if (isValid() && isEnabled(message)) { logMessage(logger.getMessageFactory().newMessage(message)); } } @Override public void log(String message, Object p0) { - if (isValid()) { + if (isValid() && isEnabled(message, p0)) { logMessage(logger.getMessageFactory().newMessage(message, p0)); } } @Override public void log(String message, Object p0, Object p1) { - if (isValid()) { + if (isValid() && isEnabled(message, p0, p1)) { logMessage(logger.getMessageFactory().newMessage(message, p0, p1)); } } @Override public void log(String message, Object p0, Object p1, Object p2) { - if (isValid()) { + if (isValid() && isEnabled(message, p0, p1, p2)) { logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2)); } } @Override public void log(String message, Object p0, Object p1, Object p2, 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(String message, Object p0, Object p1, Object p2, Object p3, 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(String message, Object p0, Object p1, Object p2, Object p3, Object p4, 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(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, 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)); } } @@ -216,7 +217,7 @@ public void log(String message, Object p0, Object p1, Object p2, Object p3, Obje @Override public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, 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)); } } @@ -224,7 +225,7 @@ public void log(String message, Object p0, Object p1, Object p2, Object p3, Obje @Override public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, 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)); } } @@ -232,14 +233,14 @@ public void log(String message, Object p0, Object p1, Object p2, Object p3, Obje @Override public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, Object p8, 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); } } @@ -265,4 +266,87 @@ private boolean isValid() { } 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 c7f13f2ef7e..1ad514fef71 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 @@ -2881,14 +2881,20 @@ public LogBuilder always() { @Override public LogBuilder atLevel(Level level) { if (isEnabled(level)) { - return getLogBuilder(level).reset(this, level); + return getLogBuilder(level); } return LogBuilder.NOOP; } - private DefaultLogBuilder getLogBuilder(Level 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 : new DefaultLogBuilder(this, level); + 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 00000000000..7dc6b9191ab --- /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> 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 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 00000000000..a71e8457eb3 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/test/LogBuilderTest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + \ 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 a6fbb0aab72..05d15f59e96 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 @@ -25,6 +25,7 @@ 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.LocationAwareReliabilityStrategy; @@ -364,6 +365,16 @@ public void setAdditive(final boolean additive) { 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. @@ -431,6 +442,10 @@ public void logEvent(final LogEvent event) { 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 00000000000..d22762fe32d --- /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; + + +/** + *

+ * 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. + *

+ *

+ * HOW TO RUN THIS TEST + *

+ *
    + *
  • single thread: + * + *
    + * java -jar target/benchmarks.jar ".*LogBuilderMarkerFilterBenchmark.*" -p useFilter=true,false
    + * 
    + * + *
  • + *
  • multiple threads (for example, 4 threads): + * + *
    + * java -jar target/benchmarks.jar ".*LogBuilderMarkerFilterBenchmark.*" -p useFilter=true,false -t 4
    + * 
    + * + *
  • + *
+ */ +@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 00000000000..65858c47087 --- /dev/null +++ b/log4j-perf/src/main/resources/log4j2-markerFilter-perf2.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + \ 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 00000000000..c28d0facbea --- /dev/null +++ b/log4j-perf/src/main/resources/log4j2-noFilter-perf.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-to-slf4j/pom.xml b/log4j-to-slf4j/pom.xml index ee49f444f84..6f741ccff08 100644 --- a/log4j-to-slf4j/pom.xml +++ b/log4j-to-slf4j/pom.xml @@ -46,6 +46,11 @@ org.slf4j slf4j-api + + org.assertj + assertj-core + test + org.hamcrest hamcrest @@ -56,6 +61,11 @@ junit-jupiter-engine test + + org.junit.jupiter + junit-jupiter-params + test + org.junit.vintage junit-vintage-engine 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 00000000000..ad978fb4b94 --- /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 be1a15a0547..ab1f8390d61 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,17 +17,28 @@ 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; 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 logBuilder = ThreadLocal.withInitial(SLF4JLogBuilder::new); + private final org.slf4j.Logger logger; private final LocationAwareLogger locationAwareLogger; @@ -256,4 +267,49 @@ public void logMessage(final String fqcn, final Level level, final Marker marker } } + @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 00000000000..ec5ebfac502 --- /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 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> 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 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 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 ad45676fa9e..8ade96b6934 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 @@ + 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 00000000000..7b634220381 --- /dev/null +++ b/log4j-to-slf4j/src/test/resources/logback-turbofilter.xml @@ -0,0 +1,39 @@ + + + + + + ACCEPT + callerId + INFO + + Log4j2 + TRACE + + + + + + %msg + + + + + + +