From c2331d37f52b81e036bd119ba985a4ae6d439f45 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Fri, 5 May 2023 17:22:21 +0200 Subject: [PATCH] [MNG-7778] - Include suppressed exceptions when logging failures (#1103) Instead of only including causes, suppressed exceptions are now included as well. A similar indentation strategy as in `Throwable.printStackTrace` is used. Back ported 56674cdc90ee45dc807b44850be5918d1cb3085b --- https://issues.apache.org/jira/browse/MNG-7778 --- .../org/slf4j/impl/MavenSimpleLogger.java | 45 ++++++++------ .../org/slf4j/impl/MavenSimpleLoggerTest.java | 61 +++++++++++++++++++ 2 files changed, 89 insertions(+), 17 deletions(-) create mode 100644 maven-slf4j-provider/src/test/java/org/slf4j/impl/MavenSimpleLoggerTest.java diff --git a/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java b/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java index 817f76c8b7..0093116214 100644 --- a/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java +++ b/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java @@ -62,25 +62,36 @@ public class MavenSimpleLogger extends SimpleLogger { } stream.println(); - while (t != null) { - for (StackTraceElement e : t.getStackTrace()) { - stream.print(" "); - stream.print(buffer().strong("at")); - stream.print(" " + e.getClassName() + "." + e.getMethodName()); - stream.print(buffer().a(" (").strong(getLocation(e)).a(")")); - stream.println(); - } + printStackTrace(t, stream, ""); + } - t = t.getCause(); - if (t != null) { - stream.print(buffer().strong("Caused by").a(": ").a(t.getClass().getName())); - if (t.getMessage() != null) { - stream.print(": "); - stream.print(buffer().failure(t.getMessage())); - } - stream.println(); - } + private void printStackTrace(Throwable t, PrintStream stream, String prefix) { + for (StackTraceElement e : t.getStackTrace()) { + stream.print(prefix); + stream.print(" "); + stream.print(buffer().strong("at")); + stream.print(" " + e.getClassName() + "." + e.getMethodName()); + stream.print(buffer().a(" (").strong(getLocation(e)).a(")")); + stream.println(); } + for (Throwable se : t.getSuppressed()) { + writeThrowable(se, stream, "Suppressed", prefix + " "); + } + Throwable cause = t.getCause(); + if (cause != null) { + writeThrowable(cause, stream, "Caused by", prefix); + } + } + + private void writeThrowable(Throwable t, PrintStream stream, String caption, String prefix) { + stream.print(buffer().a(prefix).strong(caption).a(": ").a(t.getClass().getName())); + if (t.getMessage() != null) { + stream.print(": "); + stream.print(buffer().failure(t.getMessage())); + } + stream.println(); + + printStackTrace(t, stream, prefix); } protected String getLocation(final StackTraceElement e) { diff --git a/maven-slf4j-provider/src/test/java/org/slf4j/impl/MavenSimpleLoggerTest.java b/maven-slf4j-provider/src/test/java/org/slf4j/impl/MavenSimpleLoggerTest.java new file mode 100644 index 0000000000..ec198efbc3 --- /dev/null +++ b/maven-slf4j-provider/src/test/java/org/slf4j/impl/MavenSimpleLoggerTest.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.slf4j.impl; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; + +import org.junit.Test; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.stringContainsInOrder; + +public class MavenSimpleLoggerTest { + + @Test + public void includesCauseAndSuppressedExceptionsWhenWritingThrowables() throws Exception { + Exception causeOfSuppressed = new NoSuchElementException("cause of suppressed"); + Exception suppressed = new IllegalStateException("suppressed", causeOfSuppressed); + suppressed.addSuppressed(new IllegalArgumentException( + "suppressed suppressed", new ArrayIndexOutOfBoundsException("suppressed suppressed cause"))); + Exception cause = new IllegalArgumentException("cause"); + cause.addSuppressed(suppressed); + Exception throwable = new RuntimeException("top-level", cause); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + + new MavenSimpleLogger("logger").writeThrowable(throwable, new PrintStream(output)); + + String actual = output.toString(UTF_8.name()); + + List expectedLines = Arrays.asList( + "java.lang.RuntimeException: top-level", + "Caused by: java.lang.IllegalArgumentException: cause", + " Suppressed: java.lang.IllegalStateException: suppressed", + " Suppressed: java.lang.IllegalArgumentException: suppressed suppressed", + " Caused by: java.lang.ArrayIndexOutOfBoundsException: suppressed suppressed cause", + " Caused by: java.util.NoSuchElementException: cause of suppressed"); + + assertThat(actual, stringContainsInOrder(expectedLines)); + } +}