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)
This commit is contained in:
Walter Oggioni 2020-02-17 11:34:01 +00:00 committed by GitHub
parent 4c492a797b
commit 98e9b1caa6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 86 additions and 1 deletions

View File

@ -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<Throwable> {
val self = this
return object : Iterator<Throwable> {
private var cursor : Throwable? = self
private val knownThrowables = mutableSetOf<Throwable>()
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
}
}

View File

@ -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<Throwable>) {
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<Array<Any>> {
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<TestThrowable>()
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())
}
}