diff --git a/src/main/java/sirius/kernel/health/Exceptions.java b/src/main/java/sirius/kernel/health/Exceptions.java index 977cbdf3..1a8d8445 100644 --- a/src/main/java/sirius/kernel/health/Exceptions.java +++ b/src/main/java/sirius/kernel/health/Exceptions.java @@ -8,6 +8,7 @@ package sirius.kernel.health; +import com.google.common.base.Throwables; import sirius.kernel.async.CallContext; import sirius.kernel.commons.Explain; import sirius.kernel.commons.Strings; @@ -17,6 +18,7 @@ import sirius.kernel.nls.NLS; import javax.annotation.Nullable; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -121,11 +123,11 @@ protected ErrorHandler(boolean processError) { * one can add %s (%s) to the message provided here to output the actual error and * exception type if needed. * - * @param e the exception which needs to be attached to this error handler + * @param throwable the exception which needs to be attached to this error handler * @return this in order to fluently call more methods on this handler */ - public ErrorHandler error(Throwable e) { - this.ex = e; + public ErrorHandler error(Throwable throwable) { + this.ex = throwable; return this; } @@ -251,14 +253,14 @@ public HandledException handle() { IGNORED_EXCEPTIONS_LOG.INFO(result); } return result; - } catch (Exception t) { + } catch (Exception exception) { // We call as few external methods a possible here, since things are really messed up right now - t.printStackTrace(); + exception.printStackTrace(); return new HandledException("Kernel Panic: Exception-Handling threw another exception: " - + t.getMessage() + + exception.getMessage() + " (" - + t.getClass().getName() - + ")", Collections.emptyMap(), t); + + exception.getClass().getName() + + ")", Collections.emptyMap(), exception); } } @@ -325,9 +327,9 @@ private String computeLocation(HandledException result) { /* * Adds the exception message and the exception class to the given params array. Handles null values for - * e gracefully + * the throwable gracefully */ - private Object[] extendParams(Throwable e, Object[] params) { + private Object[] extendParams(Throwable throwable, Object[] params) { Object[] newParams; if (params == null) { newParams = new Object[2]; @@ -335,9 +337,9 @@ private Object[] extendParams(Throwable e, Object[] params) { newParams = new Object[params.length + 2]; System.arraycopy(params, 0, newParams, 0, params.length); } - if (e != null) { - newParams[newParams.length - 2] = e.getMessage(); - newParams[newParams.length - 1] = e.getClass().getName(); + if (throwable != null) { + newParams[newParams.length - 2] = throwable.getMessage(); + newParams[newParams.length - 1] = throwable.getClass().getName(); } else { newParams[newParams.length - 2] = NLS.get("HandledException.unknownError"); newParams[newParams.length - 1] = "UnknownError"; @@ -391,24 +393,24 @@ public static ErrorHandler handle() { /** * Boilerplate method the directly handle the given exception without a special message or logger * - * @param e the exception to handle + * @param throwable the exception to handle * @return a HandledException which notifies surrounding calls that an error occurred, which has * already been taken care of. */ - public static HandledException handle(Throwable e) { - return handle().error(e).handle(); + public static HandledException handle(Throwable throwable) { + return handle().error(throwable).handle(); } /** * Boilerplate method the directly handle the given exception without a special message * - * @param log the logger used to log the exception - * @param e the exception to handle + * @param log the logger used to log the exception + * @param throwable the exception to handle * @return a HandledException which notifies surrounding calls that an error occurred, which has * already been taken care of. */ - public static HandledException handle(Log log, Throwable e) { - return handle().error(e).to(log).handle(); + public static HandledException handle(Log log, Throwable throwable) { + return handle().error(throwable).to(log).handle(); } /** @@ -431,11 +433,11 @@ public static ErrorHandler createHandled() { * exception is wanted to be ignored. Additionally, the ignoredExceptions logger can be turned on, * to still see those exceptions. * - * @param t the exception to be ignored. This exception will be discarded unless the ignoredExceptions - * logger is set to INFO. + * @param throwable the exception to be ignored. This exception will be discarded unless the ignoredExceptions + * logger is set to INFO. */ - public static void ignore(Throwable t) { - IGNORED_EXCEPTIONS_LOG.INFO(t); + public static void ignore(Throwable throwable) { + IGNORED_EXCEPTIONS_LOG.INFO(throwable); } /** @@ -461,8 +463,8 @@ public static void logDeprecatedMethodUse() { List> mdc = CallContext.getCurrent().getMDC(); if (mdc != null) { msg.append("\n---------------------------------------------------\n"); - for (Tuple t : mdc) { - msg.append(t.getFirst()).append(": ").append(t.getSecond()).append("\n"); + for (Tuple tuple : mdc) { + msg.append(tuple.getFirst()).append(": ").append(tuple.getSecond()).append("\n"); } } @@ -472,15 +474,15 @@ public static void logDeprecatedMethodUse() { /** * Retrieves the actual root {@link Throwable} which ended in the given exception. * - * @param e the throwable to begin with + * @param throwable the throwable to begin with * @return the root {@link Throwable} of the given one */ - public static Throwable getRootCause(@Nullable Throwable e) { - if (e == null) { + public static Throwable getRootCause(@Nullable Throwable throwable) { + if (throwable == null) { return null; } - Throwable cause = e; + Throwable cause = throwable; int circuitBreaker = 11; while (circuitBreaker > 0 && cause.getCause() != null && !cause.equals(cause.getCause())) { @@ -490,4 +492,46 @@ public static Throwable getRootCause(@Nullable Throwable e) { return cause; } + + /** + * Generates a stack trace for the given throwable and its causes without the error messages. + * + * @param throwable the throwable to generate the stack trace for + * @return a string representation of the stack trace without the error message + */ + public static String buildStackTraceWithoutErrorMessage(Throwable throwable) { + StringBuilder stringBuilder = new StringBuilder(); + + stringBuilder.append(throwable.getClass().getName()).append(":").append("\n"); + appendStackTrace(stringBuilder, throwable); + + try { + // The first element of the causal chain is always the throwable followed by its cause hierarchy. + // Therefore, the first element is skipped. + Throwables.getCausalChain(throwable).stream().skip(1).forEach(cause -> { + stringBuilder.append("Caused by: ").append(cause.getClass().getName()).append(":").append("\n"); + appendStackTrace(stringBuilder, cause); + }); + } catch (IllegalArgumentException exception) { + // This happens if the causal chain has a circular reference. + stringBuilder.append("Warning: Circular reference detected in causal chain. Skipping causes: ") + .append(exception.getMessage()); + } + + return stringBuilder.toString(); + } + + /** + * Appends the stack trace of the given throwable to the given string builder. + * + * @param stringBuilder the string builder to append the stack trace to + * @param throwable the throwable to append the stack trace of + */ + private static void appendStackTrace(StringBuilder stringBuilder, Throwable throwable) { + Arrays.stream(throwable.getStackTrace()) + .forEach(stackTraceElement -> stringBuilder.append("\t") + .append("at ") + .append(stackTraceElement) + .append("\n")); + } }