From 79a854bf52823c6c4e4b12c11846cc0b7ce091a5 Mon Sep 17 00:00:00 2001 From: oliviarla Date: Wed, 20 Nov 2024 19:43:01 +0900 Subject: [PATCH] INTERNAL: stacktrace all exceptions --- .../memcached/ExceptionMessageFactory.java | 4 ++ .../internal/CompositeException.java | 42 +++++++++++++++++- .../spy/memcached/compat/log/LoggingTest.java | 27 ++++++++++++ .../internal/CompositeExceptionTest.java | 44 +++++++++++++++++++ 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 src/test/java/net/spy/memcached/internal/CompositeExceptionTest.java diff --git a/src/main/java/net/spy/memcached/ExceptionMessageFactory.java b/src/main/java/net/spy/memcached/ExceptionMessageFactory.java index 68a1b989f..bce6b185e 100644 --- a/src/main/java/net/spy/memcached/ExceptionMessageFactory.java +++ b/src/main/java/net/spy/memcached/ExceptionMessageFactory.java @@ -70,6 +70,10 @@ private static boolean isBulkOperation(Operation op, Collection ops) } public static String createCompositeMessage(List exceptions) { + if (exceptions == null || exceptions.isEmpty()) { + throw new IllegalArgumentException("At least one exception must be specified"); + } + StringBuilder rv = new StringBuilder(); rv.append("Multiple exceptions ("); rv.append(exceptions.size()); diff --git a/src/main/java/net/spy/memcached/internal/CompositeException.java b/src/main/java/net/spy/memcached/internal/CompositeException.java index 2c92b4fe4..e501f09f8 100644 --- a/src/main/java/net/spy/memcached/internal/CompositeException.java +++ b/src/main/java/net/spy/memcached/internal/CompositeException.java @@ -1,5 +1,7 @@ package net.spy.memcached.internal; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; @@ -10,13 +12,51 @@ public class CompositeException extends ExecutionException { private static final long serialVersionUID = -599478797582490012L; private final ArrayList exceptions = new ArrayList<>(); + private final Throwable cause; - CompositeException(List exceptions) { + public CompositeException(List exceptions) { super(ExceptionMessageFactory.createCompositeMessage(exceptions)); + + if (exceptions.size() > 1) { + StringWriter sw = new StringWriter(); + sw.write(System.lineSeparator()); + try (PrintWriter pw = new PrintWriter(sw)) { + for (Exception e : exceptions) { + e.printStackTrace(pw); + } + } + + this.cause = new ExceptionOverview(sw.toString()); + } else { + this.cause = exceptions.get(0); + } this.exceptions.addAll(exceptions); } public List getExceptions() { return exceptions; } + + public int size() { + return exceptions.size(); + } + + @Override + public synchronized Throwable getCause() { + return cause; + } + + static final class ExceptionOverview extends RuntimeException { + + private static final long serialVersionUID = -641960514509105302L; + + ExceptionOverview(String message) { + super(message); + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + } } diff --git a/src/test/java/net/spy/memcached/compat/log/LoggingTest.java b/src/test/java/net/spy/memcached/compat/log/LoggingTest.java index eb1c84b27..9c575260e 100644 --- a/src/test/java/net/spy/memcached/compat/log/LoggingTest.java +++ b/src/test/java/net/spy/memcached/compat/log/LoggingTest.java @@ -4,6 +4,13 @@ // XXX: This really needs to get log4j configured first. +import java.util.ArrayList; +import java.util.List; + +import net.spy.memcached.internal.CompositeException; +import net.spy.memcached.ops.OperationErrorType; +import net.spy.memcached.ops.OperationException; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -154,4 +161,24 @@ void testNoExceptionArg() throws Exception { assertNull(t); } + @Test + void logCompositeException() { + List exceptions = new ArrayList<>(); + exceptions.add(new OperationException(OperationErrorType.SERVER, "msg1")); + exceptions.add(new OperationException(OperationErrorType.CLIENT, "msg2")); + CompositeException exception = new CompositeException(exceptions); + + logger.error("failed to get", exception); + } + + @Test + void slf4jCompositeException() { + List exceptions = new ArrayList<>(); + exceptions.add(new OperationException(OperationErrorType.SERVER, "msg1")); + exceptions.add(new OperationException(OperationErrorType.CLIENT, "msg2")); + CompositeException exception = new CompositeException(exceptions); + + Log4JLogger log4JLogger = new Log4JLogger(getClass().getName()); + log4JLogger.error("failed to get", exception); + } } diff --git a/src/test/java/net/spy/memcached/internal/CompositeExceptionTest.java b/src/test/java/net/spy/memcached/internal/CompositeExceptionTest.java new file mode 100644 index 000000000..572b0b28b --- /dev/null +++ b/src/test/java/net/spy/memcached/internal/CompositeExceptionTest.java @@ -0,0 +1,44 @@ +package net.spy.memcached.internal; + +import java.util.ArrayList; +import java.util.List; + +import net.spy.memcached.ops.OperationErrorType; +import net.spy.memcached.ops.OperationException; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CompositeExceptionTest { + + @Test + void printStackTraceOfAllExceptions() { + List exceptions = new ArrayList<>(); + exceptions.add(new OperationException(OperationErrorType.SERVER, "msg1")); + exceptions.add(new OperationException(OperationErrorType.CLIENT, "msg2")); + Exception e = throwError1(); + exceptions.add(e); + CompositeException compositeException = new CompositeException(exceptions); + String message = compositeException.getCause().getMessage(); + + assertTrue(message + .contains("OperationException: SERVER: msg1")); + assertTrue(message + .contains("OperationException: CLIENT: msg2")); + assertTrue(message + .contains("OperationException: SERVER: msg3")); + assertTrue(message + .contains("at net.spy.memcached.internal.CompositeExceptionTest.throwError2")); + assertTrue(message + .contains("at net.spy.memcached.internal.CompositeExceptionTest.throwError1")); + } + + private Exception throwError1() { + return throwError2(); + } + + private Exception throwError2() { + return new OperationException(OperationErrorType.SERVER, "msg3"); + } +}