From 84f9d18696e7ce295abf7c4b368692e3289e62cb Mon Sep 17 00:00:00 2001 From: akshaysinghh Date: Tue, 21 Jul 2026 19:08:36 +0000 Subject: [PATCH] fix: collapse redundant stack trace lines in error logs --- .../google/common/GoogleErrorLogger.java | 4 +- .../launcher/monitor/ConsoleMonitor.java | 2 +- .../InMemoryIdempotentImportExecutor.java | 4 +- ...ryingInMemoryIdempotentImportExecutor.java | 8 +- .../types/common/ExceptionUtils.java | 87 +++++++++++++++++++ .../types/transfer/retry/RetryMapping.java | 2 +- 6 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 portability-types-common/src/main/java/org/datatransferproject/types/common/ExceptionUtils.java diff --git a/extensions/data-transfer/portability-data-transfer-google/src/main/java/org/datatransferproject/datatransfer/google/common/GoogleErrorLogger.java b/extensions/data-transfer/portability-data-transfer-google/src/main/java/org/datatransferproject/datatransfer/google/common/GoogleErrorLogger.java index 916d4f145..83a4deb29 100644 --- a/extensions/data-transfer/portability-data-transfer-google/src/main/java/org/datatransferproject/datatransfer/google/common/GoogleErrorLogger.java +++ b/extensions/data-transfer/portability-data-transfer-google/src/main/java/org/datatransferproject/datatransfer/google/common/GoogleErrorLogger.java @@ -1,7 +1,7 @@ package org.datatransferproject.datatransfer.google.common; -import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; +import org.datatransferproject.types.common.ExceptionUtils; import java.io.IOException; import java.util.UUID; import org.datatransferproject.spi.cloud.storage.JobStore; @@ -30,7 +30,7 @@ public static ErrorDetail createErrorDetail(String idempotentId, String title, E return ErrorDetail.builder() .setId(idempotentId) .setTitle(title) - .setException(Throwables.getStackTraceAsString(e)) + .setException(ExceptionUtils.getStackTraceAsString(e)) .setCanSkip(canSkip).build(); } } diff --git a/portability-api-launcher/src/main/java/org/datatransferproject/launcher/monitor/ConsoleMonitor.java b/portability-api-launcher/src/main/java/org/datatransferproject/launcher/monitor/ConsoleMonitor.java index cd501518c..35c7adafd 100644 --- a/portability-api-launcher/src/main/java/org/datatransferproject/launcher/monitor/ConsoleMonitor.java +++ b/portability-api-launcher/src/main/java/org/datatransferproject/launcher/monitor/ConsoleMonitor.java @@ -16,7 +16,7 @@ package org.datatransferproject.launcher.monitor; import static com.google.common.base.Strings.isNullOrEmpty; -import static com.google.common.base.Throwables.getStackTraceAsString; +import static org.datatransferproject.types.common.ExceptionUtils.getStackTraceAsString; import java.util.UUID; import org.datatransferproject.api.launcher.Monitor; diff --git a/portability-spi-transfer/src/main/java/org/datatransferproject/spi/transfer/idempotentexecutor/InMemoryIdempotentImportExecutor.java b/portability-spi-transfer/src/main/java/org/datatransferproject/spi/transfer/idempotentexecutor/InMemoryIdempotentImportExecutor.java index b8edc1463..8b9d3c7bb 100644 --- a/portability-spi-transfer/src/main/java/org/datatransferproject/spi/transfer/idempotentexecutor/InMemoryIdempotentImportExecutor.java +++ b/portability-spi-transfer/src/main/java/org/datatransferproject/spi/transfer/idempotentexecutor/InMemoryIdempotentImportExecutor.java @@ -19,8 +19,8 @@ import static java.lang.String.format; import com.google.common.base.Joiner; -import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; +import org.datatransferproject.types.common.ExceptionUtils; import java.util.UUID; import org.datatransferproject.api.launcher.Monitor; import org.datatransferproject.types.transfer.errors.ErrorDetail; @@ -81,7 +81,7 @@ public T executeOrThrowException( ErrorDetail.builder() .setId(idempotentId) .setTitle(itemName) - .setException(Throwables.getStackTraceAsString(e)) + .setException(ExceptionUtils.getStackTraceAsString(e)) .build(); errors.put(idempotentId, errorDetail); recentErrors.put(idempotentId, errorDetail); diff --git a/portability-spi-transfer/src/main/java/org/datatransferproject/spi/transfer/idempotentexecutor/RetryingInMemoryIdempotentImportExecutor.java b/portability-spi-transfer/src/main/java/org/datatransferproject/spi/transfer/idempotentexecutor/RetryingInMemoryIdempotentImportExecutor.java index a84377783..187a6d882 100644 --- a/portability-spi-transfer/src/main/java/org/datatransferproject/spi/transfer/idempotentexecutor/RetryingInMemoryIdempotentImportExecutor.java +++ b/portability-spi-transfer/src/main/java/org/datatransferproject/spi/transfer/idempotentexecutor/RetryingInMemoryIdempotentImportExecutor.java @@ -19,8 +19,8 @@ import static java.lang.String.format; import com.google.common.base.Joiner; -import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; +import org.datatransferproject.types.common.ExceptionUtils; import java.io.IOException; import java.io.Serializable; import java.time.Clock; @@ -90,10 +90,10 @@ public T executeOrThrowException( errors.remove(idempotentId); return result; } catch (RetryException e) { - ErrorDetail.Builder errorDetailBuilder = ErrorDetail.builder(); - errorDetailBuilder.setId(idempotentId) + ErrorDetail.Builder errorDetailBuilder = ErrorDetail.builder() + .setId(idempotentId) .setTitle(itemName) - .setException(Throwables.getStackTraceAsString(e)); + .setException(ExceptionUtils.getStackTraceAsString(e)); if(e.canSkip()){ ErrorDetail errorDetail = errorDetailBuilder.setCanSkip(true).build(); errors.put(idempotentId, errorDetail); diff --git a/portability-types-common/src/main/java/org/datatransferproject/types/common/ExceptionUtils.java b/portability-types-common/src/main/java/org/datatransferproject/types/common/ExceptionUtils.java new file mode 100644 index 000000000..7d0241422 --- /dev/null +++ b/portability-types-common/src/main/java/org/datatransferproject/types/common/ExceptionUtils.java @@ -0,0 +1,87 @@ +package org.datatransferproject.types.common; + +/** Utility class for handling exceptions. */ +public class ExceptionUtils { + + /** + * Formats a Throwable's stack trace to collapse consecutive identical frames. + * This handles highly recursive calls (e.g. repeated copyHelper invocations) + * by logging the first occurrence and annotating how many times it was repeated. + */ + public static String getStackTraceAsString(Throwable t) { + if (t == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + sb.append(t.toString()).append('\n'); + StackTraceElement[] trace = t.getStackTrace(); + int duplicateCount = 0; + StackTraceElement lastElement = null; + + for (StackTraceElement element : trace) { + if (lastElement != null && element.equals(lastElement)) { + duplicateCount++; + } else { + if (duplicateCount > 0) { + sb.append("\t... repeated ").append(duplicateCount).append(" times more\n"); + duplicateCount = 0; + } + sb.append("\tat ").append(element.toString()).append('\n'); + lastElement = element; + } + } + if (duplicateCount > 0) { + sb.append("\t... repeated ").append(duplicateCount).append(" times more\n"); + } + + // Now handle Caused by recursively + Throwable cause = t.getCause(); + if (cause != null) { + appendCause(cause, trace, sb); + } + + return sb.toString(); + } + + private static void appendCause(Throwable cause, StackTraceElement[] enclosingTrace, StringBuilder sb) { + sb.append("Caused by: ").append(cause.toString()).append('\n'); + StackTraceElement[] trace = cause.getStackTrace(); + + // Find common suffix between trace and enclosingTrace + int m = trace.length - 1; + int n = enclosingTrace.length - 1; + while (m >= 0 && n >= 0 && trace[m].equals(enclosingTrace[n])) { + m--; + n--; + } + int framesInCommon = trace.length - 1 - m; + + int duplicateCount = 0; + StackTraceElement lastElement = null; + + for (int i = 0; i <= m; i++) { + StackTraceElement element = trace[i]; + if (lastElement != null && element.equals(lastElement)) { + duplicateCount++; + } else { + if (duplicateCount > 0) { + sb.append("\t... repeated ").append(duplicateCount).append(" times more\n"); + duplicateCount = 0; + } + sb.append("\tat ").append(element.toString()).append('\n'); + lastElement = element; + } + } + if (duplicateCount > 0) { + sb.append("\t... repeated ").append(duplicateCount).append(" times more\n"); + } + if (framesInCommon != 0) { + sb.append("\t... ").append(framesInCommon).append(" more\n"); + } + + Throwable nextCause = cause.getCause(); + if (nextCause != null) { + appendCause(nextCause, trace, sb); + } + } +} diff --git a/portability-types-transfer/src/main/java/org/datatransferproject/types/transfer/retry/RetryMapping.java b/portability-types-transfer/src/main/java/org/datatransferproject/types/transfer/retry/RetryMapping.java index 0cc6a44b5..294863499 100644 --- a/portability-types-transfer/src/main/java/org/datatransferproject/types/transfer/retry/RetryMapping.java +++ b/portability-types-transfer/src/main/java/org/datatransferproject/types/transfer/retry/RetryMapping.java @@ -17,7 +17,7 @@ package org.datatransferproject.types.transfer.retry; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Throwables.getStackTraceAsString; +import static org.datatransferproject.types.common.ExceptionUtils.getStackTraceAsString; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Arrays;