diff --git a/config/checkstyle/checkstyle-suppressions.xml b/config/checkstyle/checkstyle-suppressions.xml index 929c586a5c9..eac6829276e 100644 --- a/config/checkstyle/checkstyle-suppressions.xml +++ b/config/checkstyle/checkstyle-suppressions.xml @@ -79,4 +79,7 @@ + + + diff --git a/core/spring-boot/build.gradle b/core/spring-boot/build.gradle index a32fd204b66..01010c00564 100644 --- a/core/spring-boot/build.gradle +++ b/core/spring-boot/build.gradle @@ -87,6 +87,12 @@ tasks.named("checkFormatMain") { source(fileTree("src/main/javaTemplates")) } +tasks.named("compileJava") { + // Provide the project coordinates to the `GraalVmProcessor`: + options.compilerArgs << '-Alog4j.graalvm.groupId=org.springframework.boot' + options.compilerArgs << '-Alog4j.graalvm.artifactId=spring-boot-log4j' +} + plugins.withType(EclipsePlugin) { eclipse { synchronizationTasks syncJavaTemplates diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatter.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatter.java index 5fb2650825d..b098795aecf 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatter.java @@ -22,7 +22,6 @@ import java.util.TreeSet; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.impl.ThrowableProxy; import org.apache.logging.log4j.core.time.Instant; import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.jspecify.annotations.Nullable; @@ -70,13 +69,13 @@ class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogF members.add("message", LogEvent::getMessage).as(StructuredMessage::get); members.from(LogEvent::getContextData) .usingPairs(contextPairs.nested(ElasticCommonSchemaStructuredLogFormatter::addContextDataPairs)); - members.from(LogEvent::getThrownProxy).whenNotNull().usingMembers((thrownProxyMembers) -> { - thrownProxyMembers.add("error").usingMembers((error) -> { - error.add("type", ThrowableProxy::getThrowable).whenNotNull().as(ObjectUtils::nullSafeClassName); - error.add("message", ThrowableProxy::getMessage); + members.from(LogEvent::getThrown) + .whenNotNull() + .usingMembers((thrownMembers) -> thrownMembers.add("error").usingMembers((error) -> { + error.add("type", ObjectUtils::nullSafeClassName); + error.add("message", Throwable::getMessage); error.add("stack_trace", extractor::stackTrace); - }); - }); + })); members.add("tags", LogEvent::getMarker) .whenNotNull() .as(ElasticCommonSchemaStructuredLogFormatter::getMarkers) diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ExtendedWhitespaceThrowablePatternConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ExtendedWhitespaceThrowablePatternConverter.java index 3b45bb66293..a4d9f8adc97 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ExtendedWhitespaceThrowablePatternConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ExtendedWhitespaceThrowablePatternConverter.java @@ -21,6 +21,7 @@ import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.pattern.ConverterKeys; import org.apache.logging.log4j.core.pattern.ExtendedThrowablePatternConverter; +import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; import org.apache.logging.log4j.core.pattern.PatternConverter; import org.apache.logging.log4j.core.pattern.ThrowablePatternConverter; import org.jspecify.annotations.Nullable; @@ -35,24 +36,32 @@ import org.jspecify.annotations.Nullable; */ @Plugin(name = "ExtendedWhitespaceThrowablePatternConverter", category = PatternConverter.CATEGORY) @ConverterKeys({ "xwEx", "xwThrowable", "xwException" }) -public final class ExtendedWhitespaceThrowablePatternConverter extends ThrowablePatternConverter { +public final class ExtendedWhitespaceThrowablePatternConverter extends LogEventPatternConverter { private final ExtendedThrowablePatternConverter delegate; - private ExtendedWhitespaceThrowablePatternConverter(Configuration configuration, String @Nullable [] options) { - super("WhitespaceExtendedThrowable", "throwable", options, configuration); + private final String separator; + + private ExtendedWhitespaceThrowablePatternConverter(Configuration configuration, @Nullable String[] options) { + super("WhitespaceExtendedThrowable", "throwable"); this.delegate = ExtendedThrowablePatternConverter.newInstance(configuration, options); + this.separator = this.delegate.getOptions().getSeparator(); } @Override public void format(LogEvent event, StringBuilder buffer) { if (event.getThrown() != null) { - buffer.append(this.options.getSeparator()); + buffer.append(this.separator); this.delegate.format(event, buffer); - buffer.append(this.options.getSeparator()); + buffer.append(this.separator); } } + @Override + public boolean handlesThrowable() { + return true; + } + /** * Creates a new instance of the class. Required by Log4J2. * @param configuration current configuration @@ -61,7 +70,7 @@ public final class ExtendedWhitespaceThrowablePatternConverter extends Throwable * @return a new {@code WhitespaceThrowablePatternConverter} */ public static ExtendedWhitespaceThrowablePatternConverter newInstance(Configuration configuration, - String @Nullable [] options) { + @Nullable String[] options) { return new ExtendedWhitespaceThrowablePatternConverter(configuration, options); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Extractor.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Extractor.java index bda41b00561..0d9fa411e6e 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Extractor.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Extractor.java @@ -16,13 +16,14 @@ package org.springframework.boot.logging.log4j2; +import java.io.PrintWriter; +import java.io.StringWriter; + import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.impl.ThrowableProxy; import org.jspecify.annotations.Nullable; import org.slf4j.event.LoggingEvent; import org.springframework.boot.logging.StackTracePrinter; -import org.springframework.util.Assert; /** * Functions to extract items from {@link LoggingEvent}. @@ -42,19 +43,23 @@ class Extractor { } @Nullable String stackTrace(LogEvent event) { - return stackTrace(event.getThrownProxy()); + return stackTrace(event.getThrown()); } - @Nullable String stackTrace(@Nullable ThrowableProxy throwableProxy) { - if (throwableProxy == null) { + @Nullable String stackTrace(@Nullable Throwable throwable) { + if (throwable == null) { return null; } if (this.stackTracePrinter != null) { - Throwable throwable = throwableProxy.getThrowable(); - Assert.state(throwable != null, "Proxy must return Throwable in order to print exception"); return this.stackTracePrinter.printStackTraceToString(throwable); } - return throwableProxy.getExtendedStackTraceAsString(); + return printStackTrace(throwable); + } + + private static String printStackTrace(Throwable throwable) { + StringWriter stringWriter = new StringWriter(); + throwable.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); } } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/GraylogExtendedLogFormatStructuredLogFormatter.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/GraylogExtendedLogFormatStructuredLogFormatter.java index f07b46e6ba4..acd254fdabc 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/GraylogExtendedLogFormatStructuredLogFormatter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/GraylogExtendedLogFormatStructuredLogFormatter.java @@ -98,11 +98,11 @@ class GraylogExtendedLogFormatStructuredLogFormatter extends JsonWriterStructure .whenNot(mapIsEmpty) .usingPairs(contextPairs.flat(additionalFieldJoiner(), GraylogExtendedLogFormatStructuredLogFormatter::addContextDataPairs)); - Function<@Nullable LogEvent, @Nullable Object> getThrownProxy = (event) -> (event != null) - ? event.getThrownProxy() : null; + Function<@Nullable LogEvent, @Nullable Object> getThrown = (event) -> (event != null) ? event.getThrown() + : null; members.add() - .whenNotNull(getThrownProxy) - .usingMembers((thrownProxyMembers) -> throwableMembers(thrownProxyMembers, extractor)); + .whenNotNull(getThrown) + .usingMembers((thrownMembers) -> throwableMembers(thrownMembers, extractor)); } private static String getMessageText(Message message) { @@ -134,11 +134,9 @@ class GraylogExtendedLogFormatStructuredLogFormatter extends JsonWriterStructure private static void throwableMembers(Members members, Extractor extractor) { members.add("full_message", extractor::messageAndStackTrace); - members.add("_error_type", (event) -> event.getThrownProxy().getThrowable()) - .whenNotNull() - .as(ObjectUtils::nullSafeClassName); + members.add("_error_type", LogEvent::getThrown).whenNotNull().as(ObjectUtils::nullSafeClassName); members.add("_error_stack_trace", extractor::stackTrace); - members.add("_error_message", (event) -> event.getThrownProxy().getMessage()); + members.add("_error_message", (event) -> event.getThrown().getMessage()); } private static void addContextDataPairs(ContextPairs.Pairs contextPairs) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/LogstashStructuredLogFormatter.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/LogstashStructuredLogFormatter.java index 82985179071..94651023f58 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/LogstashStructuredLogFormatter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/LogstashStructuredLogFormatter.java @@ -71,7 +71,7 @@ class LogstashStructuredLogFormatter extends JsonWriterStructuredLogFormatter deserialized = deserialize(json); String stackTrace = (String) deserialized.get("stack_trace"); assertThat(stackTrace).startsWith( - """ - java.lang.RuntimeException: Boom - \tat org.springframework.boot.logging.log4j2.LogstashStructuredLogFormatterTests.shouldFormatException"""); + "java.lang.RuntimeException: Boom%n\tat org.springframework.boot.logging.log4j2.LogstashStructuredLogFormatterTests.shouldFormatException" + .formatted()); assertThat(json).contains( - """ - java.lang.RuntimeException: Boom\\n\\tat org.springframework.boot.logging.log4j2.LogstashStructuredLogFormatterTests.shouldFormatException"""); + "java.lang.RuntimeException: Boom%n\\tat org.springframework.boot.logging.log4j2.LogstashStructuredLogFormatterTests.shouldFormatException" + .formatted() + .replace("\n", "\\n") + .replace("\r", "\\r")); } @Test diff --git a/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/WhitespaceThrowablePatternConverterTests.java b/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/WhitespaceThrowablePatternConverterTests.java index 970176a04b2..782cbeae15e 100644 --- a/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/WhitespaceThrowablePatternConverterTests.java +++ b/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/WhitespaceThrowablePatternConverterTests.java @@ -19,7 +19,7 @@ package org.springframework.boot.logging.log4j2; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.core.pattern.ThrowablePatternConverter; +import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -31,7 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat; */ class WhitespaceThrowablePatternConverterTests { - private final ThrowablePatternConverter converter = WhitespaceThrowablePatternConverter + private final LogEventPatternConverter converter = WhitespaceThrowablePatternConverter .newInstance(new DefaultConfiguration(), new String[] {}); @Test diff --git a/core/spring-boot/src/test/java/org/springframework/boot/logging/logback/ExtractorTests.java b/core/spring-boot/src/test/java/org/springframework/boot/logging/logback/ExtractorTests.java index 35f445bc000..b058a9dc26b 100644 --- a/core/spring-boot/src/test/java/org/springframework/boot/logging/logback/ExtractorTests.java +++ b/core/spring-boot/src/test/java/org/springframework/boot/logging/logback/ExtractorTests.java @@ -46,7 +46,7 @@ class ExtractorTests { } @Test - void stackTraceWhenNoPrinterPrintsUsingLoggingSystem() { + void stackTraceWhenNoPrinterPrintsUsingFallback() { Extractor extractor = new Extractor(null, createConverter()); assertThat(extractor.stackTrace(createEvent())).contains("java.lang.RuntimeException: Boom!"); } diff --git a/platform/spring-boot-dependencies/build.gradle b/platform/spring-boot-dependencies/build.gradle index e0a0f806b6e..fd772784771 100644 --- a/platform/spring-boot-dependencies/build.gradle +++ b/platform/spring-boot-dependencies/build.gradle @@ -1252,23 +1252,14 @@ bom { releaseNotes("https://github.com/liquibase/liquibase/releases/tag/v{version}") } } - library("Log4j2", "2.24.3") { + library("Log4j2", "2.25.1") { prohibit { contains "-alpha" contains "-beta" because "we don't want alphas or betas" } group("org.apache.logging.log4j") { - bom("log4j-bom") { - permit("biz.aQute.bnd:biz.aQute.bnd.annotation") - permit("com.github.spotbugs:spotbugs-annotations") - permit("org.apache.logging:logging-parent") - permit("org.apache.maven.plugin-tools:maven-plugin-annotations") - permit("org.jspecify:jspecify") - permit("org.osgi:org.osgi.annotation.bundle") - permit("org.osgi:org.osgi.annotation.versioning") - permit("org.osgi:osgi.annotation") - } + bom("log4j-bom") } links { site("https://logging.apache.org/log4j") diff --git a/smoke-test/spring-boot-smoke-test-structured-logging-log4j2/src/main/java/smoketest/structuredlogging/log4j2/CustomStructuredLogFormatter.java b/smoke-test/spring-boot-smoke-test-structured-logging-log4j2/src/main/java/smoketest/structuredlogging/log4j2/CustomStructuredLogFormatter.java index 2e7af6c1d80..239c433b6b5 100644 --- a/smoke-test/spring-boot-smoke-test-structured-logging-log4j2/src/main/java/smoketest/structuredlogging/log4j2/CustomStructuredLogFormatter.java +++ b/smoke-test/spring-boot-smoke-test-structured-logging-log4j2/src/main/java/smoketest/structuredlogging/log4j2/CustomStructuredLogFormatter.java @@ -16,8 +16,10 @@ package smoketest.structuredlogging.log4j2; +import java.io.PrintWriter; +import java.io.StringWriter; + import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.impl.ThrowableProxy; import org.jspecify.annotations.Nullable; import org.springframework.boot.logging.structured.StructuredLogFormatter; @@ -39,9 +41,11 @@ public class CustomStructuredLogFormatter implements StructuredLogFormatter