From 98e9b1caa6bbaf05508c79329d4e78db3e9dcd4b Mon Sep 17 00:00:00 2001 From: Walter Oggioni <6357328+woggioni@users.noreply.github.com> Date: Mon, 17 Feb 2020 11:34:01 +0000 Subject: [PATCH] EG-65 - Improved error reporting on stdout (#5962) I modified the `ErrorCodeRewritePolicy` to concatenate all the error messages of the exception's cause chain. The code will start from the throwable being passed to the logger and concatenate its error message with the one in its cause and proceed recursively until it finds an exception with no cause or it detects a loop (an exception already encountered in the traversal). This should provide customers with more information about errors without having to look at the logs (that should still remain the primary source of information) --- .../logging/ExceptionsErrorCodeFunctions.kt | 28 ++++++++- .../logging/WalkExceptionCausedByListTest.kt | 59 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 common/logging/src/test/kotlin/net/corda/commmon/logging/WalkExceptionCausedByListTest.kt diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/ExceptionsErrorCodeFunctions.kt b/common/logging/src/main/kotlin/net/corda/common/logging/ExceptionsErrorCodeFunctions.kt index 5fea6054e4..a34e436bb8 100644 --- a/common/logging/src/main/kotlin/net/corda/common/logging/ExceptionsErrorCodeFunctions.kt +++ b/common/logging/src/main/kotlin/net/corda/common/logging/ExceptionsErrorCodeFunctions.kt @@ -4,11 +4,37 @@ import org.apache.logging.log4j.Level import org.apache.logging.log4j.message.Message import org.apache.logging.log4j.message.SimpleMessage import java.util.* +//Returns an iterator that traverses all the exception's cause chain stopping in case of loops (an exception caused by itself) +fun Throwable.walkExceptionCausedByList() : Iterator { + val self = this + return object : Iterator { + private var cursor : Throwable? = self + private val knownThrowables = mutableSetOf() + + override fun hasNext(): Boolean { + return cursor != null + } + + override fun next(): Throwable { + val result = cursor + val cause = cursor?.cause + cursor = if(cause != null && knownThrowables.add(cause)) { + cause + } else { + null + } + return result!! + } + } +} fun Message.withErrorCodeFor(error: Throwable?, level: Level): Message { return when { - error != null && level.isInRange(Level.FATAL, Level.WARN) -> CompositeMessage("$formattedMessage [errorCode=${error.errorCode()}, moreInformationAt=${error.errorCodeLocationUrl()}]", format, parameters, throwable) + error != null && level.isInRange(Level.FATAL, Level.WARN) -> { + val message = error.walkExceptionCausedByList().asSequence().mapNotNull(Throwable::message).joinToString(" - ") + CompositeMessage("$message [errorCode=${error.errorCode()}, moreInformationAt=${error.errorCodeLocationUrl()}]", format, parameters, throwable) + } else -> this } } diff --git a/common/logging/src/test/kotlin/net/corda/commmon/logging/WalkExceptionCausedByListTest.kt b/common/logging/src/test/kotlin/net/corda/commmon/logging/WalkExceptionCausedByListTest.kt new file mode 100644 index 0000000000..371deef20d --- /dev/null +++ b/common/logging/src/test/kotlin/net/corda/commmon/logging/WalkExceptionCausedByListTest.kt @@ -0,0 +1,59 @@ +package net.corda.commmon.logging + +import net.corda.common.logging.walkExceptionCausedByList +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +class WalkExceptionCausedByListTest(@Suppress("UNUSED_PARAMETER") testTitle: String, private val e: Throwable, private val expectedExceptionSequence: List) { + + private class TestThrowable(val id : Int, cause : Throwable?) : Throwable(cause) { + override fun toString(): String { + return "${this.javaClass.simpleName}(${this.id})" + } + } + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun data(): Collection> { + val field = Throwable::class.java.getDeclaredField("cause") + field.isAccessible = true + return listOf( + run { + val e = TestThrowable(0, null) + arrayOf("Simple exception with no cause", e, listOf(e)) + }, + run { + var e: TestThrowable? = null + val exceptions = (0 until 10).map { + e = TestThrowable(it, e) + e + } + arrayOf("Exception with cause list", e!!, exceptions.asReversed()) + }, + run { + val e = TestThrowable(0, null) + field.set(e, e) + arrayOf("Exception caused by itself", e, listOf(e)) + }, + run { + val stack = mutableListOf() + var e: TestThrowable? = null + for(i in 0 until 10) { + e = TestThrowable(i, stack.lastOrNull()) + stack.add(e!!) + } + field.set(stack[0], stack[4]) + arrayOf("Exception with loop in cause list", e!!, stack.asReversed()) + }) + } + } + + @Test(timeout = 1000) + fun test() { + Assert.assertEquals(expectedExceptionSequence, e.walkExceptionCausedByList().asSequence().toList()) + } +} \ No newline at end of file