From 95247ce21bd4a9e47ff0fc94fa40d8712c1b8fde Mon Sep 17 00:00:00 2001 From: Werner Glanzer Date: Tue, 8 Aug 2023 10:12:53 +0200 Subject: [PATCH 1/4] feat: combine mostly all log files into a "log_others.zip", except "messages.log" --- .../eventlogger/sentry/SentryEventLogger.java | 62 ++++++++++++++++++- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/adito/aditoweb/nbm/metrics/impl/eventlogger/sentry/SentryEventLogger.java b/src/main/java/de/adito/aditoweb/nbm/metrics/impl/eventlogger/sentry/SentryEventLogger.java index bb601d2..3de8fa3 100644 --- a/src/main/java/de/adito/aditoweb/nbm/metrics/impl/eventlogger/sentry/SentryEventLogger.java +++ b/src/main/java/de/adito/aditoweb/nbm/metrics/impl/eventlogger/sentry/SentryEventLogger.java @@ -24,6 +24,7 @@ import java.util.function.Supplier; import java.util.logging.*; import java.util.stream.Collectors; +import java.util.zip.*; /** * @author w.glanzer, 23.06.2022 @@ -174,14 +175,69 @@ private List extractAttachments(@NonNull IBugReport pReport) // Append Logs Optional.ofNullable(pReport.getLogs()) - .map(pLogFiles -> pLogFiles.stream() - .map(pLogFile -> new Attachment(pLogFile.getData(), "log_" + pLogFile.getName(), MediaType.PLAIN_TEXT_UTF_8.toString())) - .collect(Collectors.toList())) + .map(pLogFiles -> { + List result = new ArrayList<>(); + List remainingFiles = new ArrayList<>(pLogFiles); + + // Add a "standalone" messages.log, if possible - so it will be accessable more quickly + for (Iterator iterator = remainingFiles.iterator(); iterator.hasNext(); ) + { + IBugReport.LogFile file = iterator.next(); + if ("messages.log".equalsIgnoreCase(file.getName())) + { + result.add(new Attachment(file.getData(), "log_" + file.getName(), MediaType.PLAIN_TEXT_UTF_8.toString())); + iterator.remove(); + } + } + + // Combine all other log files into one large zip file + if (!remainingFiles.isEmpty()) + { + Attachment others = compress(remainingFiles.stream().collect(Collectors.toMap(pLogFile -> "log_" + pLogFile.getName(), IBugReport.LogFile::getData))); + if (others != null) + result.add(others); + } + + return result; + }) .ifPresent(attachments::addAll); return attachments; } + /** + * Combines the given data into a large zip file + * + * @param pFiles Files to compress to a zip file + * @return the zip file as attachment or null, if it could not be created + */ + @Nullable + private Attachment compress(@NonNull Map pFiles) + { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ZipOutputStream zos = new ZipOutputStream(baos)) + { + // Add everything to the ZipOutputStream + for (Map.Entry file : pFiles.entrySet()) + { + zos.putNextEntry(new ZipEntry(file.getKey())); + zos.write(file.getValue()); + zos.closeEntry(); + } + + // close the zip, because we finished + zos.close(); + + // return a new attachment + return new Attachment(baos.toByteArray(), "log_others.zip", MediaType.ZIP.toString()); + } + catch (IOException e) + { + LOGGER.log(Level.WARNING, "", e); + return null; + } + } + /** * Extracts all extra information to send * From 6a73791a0a27f733f2029da2271cce5df91c273b Mon Sep 17 00:00:00 2001 From: Werner Glanzer Date: Tue, 8 Aug 2023 12:31:22 +0200 Subject: [PATCH 2/4] feat: added dynamic stack traces, so sentry will not combine the happened events --- .../eventlogger/sentry/SentryEventLogger.java | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/adito/aditoweb/nbm/metrics/impl/eventlogger/sentry/SentryEventLogger.java b/src/main/java/de/adito/aditoweb/nbm/metrics/impl/eventlogger/sentry/SentryEventLogger.java index 3de8fa3..c4d7cd1 100644 --- a/src/main/java/de/adito/aditoweb/nbm/metrics/impl/eventlogger/sentry/SentryEventLogger.java +++ b/src/main/java/de/adito/aditoweb/nbm/metrics/impl/eventlogger/sentry/SentryEventLogger.java @@ -19,11 +19,10 @@ import java.io.*; import java.lang.management.ThreadInfo; import java.nio.charset.StandardCharsets; -import java.time.Instant; import java.util.*; import java.util.function.Supplier; import java.util.logging.*; -import java.util.stream.Collectors; +import java.util.stream.*; import java.util.zip.*; /** @@ -102,7 +101,7 @@ public String captureBugReport(@NonNull IBugReport pReport, @Nullable File pOutp { return _catchException(() -> { // Prepare event - SentryEvent ev = _createEvent(SentryLevel.INFO, null, null, "User Feedback - " + Instant.now().toString()); + SentryEvent ev = _createEvent(SentryLevel.INFO, null, new UserFeedbackEvent(), null); ev.setExtras(extractExtraInformation(pReport)); SentryId eventId = ev.getEventId(); assert eventId != null; @@ -436,6 +435,46 @@ public void run() } } + /** + * Exception that gets transmitted to sentry, if a userfeedback should be sent + */ + private static class UserFeedbackEvent extends Exception + { + /** + * Length of the random trace generation + */ + private static final int RANDOM_TRACE_DEPTH = 3; + + public UserFeedbackEvent() + { + setStackTrace(appendRandomStackTraceElements(getStackTrace())); + } + + /** + * Appends random stacktrace elements to the given element array, + * so Sentry is forced to not combining those exceptions + * + * @param pOriginalElements Original elements + * @return the elements, with the appended generated ones + */ + @NonNull + private StackTraceElement[] appendRandomStackTraceElements(StackTraceElement @NonNull [] pOriginalElements) + { + List elements = new ArrayList<>(); + + // Add random stacktrace elements + IntStream.range(0, RANDOM_TRACE_DEPTH) + .mapToObj(pIdx -> new StackTraceElement(getClass().getName() + ".Dyn_" + new Random().nextInt(Integer.MAX_VALUE), + "init", "Dynamic_" + new Random().nextInt(Integer.MAX_VALUE) + ".java", -1)) + .forEachOrdered(elements::add); + + // Append all current stacktrace elements, so it stays human readable in sentry logging + elements.addAll(Arrays.asList(pOriginalElements)); + + return elements.toArray(new StackTraceElement[0]); + } + } + /** * Supplier that is capable of handling exceptions * From b6ea3a2c4ccb43997ace99ffa148ca1b8cc7cab4 Mon Sep 17 00:00:00 2001 From: Werner Glanzer Date: Tue, 8 Aug 2023 13:33:56 +0200 Subject: [PATCH 3/4] fix: replaced TIMED_WAITING with WAITING, because IntelliJ has to interpret it the same way --- .../metrics/impl/detectors/ThreadUtility.java | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/adito/aditoweb/nbm/metrics/impl/detectors/ThreadUtility.java b/src/main/java/de/adito/aditoweb/nbm/metrics/impl/detectors/ThreadUtility.java index 8347ae1..020d9b0 100644 --- a/src/main/java/de/adito/aditoweb/nbm/metrics/impl/detectors/ThreadUtility.java +++ b/src/main/java/de/adito/aditoweb/nbm/metrics/impl/detectors/ThreadUtility.java @@ -1,6 +1,7 @@ package de.adito.aditoweb.nbm.metrics.impl.detectors; import lombok.NonNull; +import org.jetbrains.annotations.Nullable; import java.lang.management.*; import java.util.*; @@ -22,12 +23,16 @@ public class ThreadUtility @NonNull static String getThreadInfoStacktrace(@NonNull ThreadInfo pThreadInfo) { + Thread.State state = getThreadState(pThreadInfo); + if (state == null) + state = Thread.State.TERMINATED; + StringBuilder sb = new StringBuilder("\"" + pThreadInfo.getThreadName() + "\"" + (pThreadInfo.isDaemon() ? " daemon" : "") + " prio=" + pThreadInfo.getPriority() + " tid=0x" + String.format("%X", pThreadInfo.getThreadId()) + " nid=NA " + - pThreadInfo.getThreadState().toString().toLowerCase()); + state.toString().toLowerCase()); if (pThreadInfo.getLockName() != null) { sb.append(" on " + pThreadInfo.getLockName()); @@ -46,7 +51,7 @@ static String getThreadInfoStacktrace(@NonNull ThreadInfo pThreadInfo) sb.append(" (in native)"); } sb.append('\n'); - sb.append(" java.lang.Thread.State: ").append(pThreadInfo.getThreadState()).append("\n"); + sb.append(" java.lang.Thread.State: ").append(state).append("\n"); StackTraceElement[] stackTrace = pThreadInfo.getStackTrace(); int i = 0; for (; i < stackTrace.length; i++) @@ -56,7 +61,7 @@ static String getThreadInfoStacktrace(@NonNull ThreadInfo pThreadInfo) sb.append('\n'); if (i == 0 && pThreadInfo.getLockInfo() != null) { - Thread.State ts = pThreadInfo.getThreadState(); + Thread.State ts = state; switch (ts) { case BLOCKED: @@ -123,4 +128,20 @@ public static String getDeadlockedThreadsAsString(@NonNull List pDeadlockedTh .map(String::valueOf) .collect(Collectors.joining("\n")) + "\n"; } + + /** + * Extracts the thread state of the given info. + * This method is necessary, because IntelliJ should interpret TIMED_WAITING and WAITING the same way + * + * @param pInfo Info to read the state from + * @return the state + */ + @Nullable + private static Thread.State getThreadState(@NonNull ThreadInfo pInfo) + { + Thread.State state = pInfo.getThreadState(); + if (state == Thread.State.TIMED_WAITING) + state = Thread.State.WAITING; + return state; + } } From bbaac9a1058c71bc2241161663dda8e4e4e08f0b Mon Sep 17 00:00:00 2001 From: Werner Glanzer Date: Tue, 8 Aug 2023 14:25:01 +0200 Subject: [PATCH 4/4] chore: fixed typo in SentryEventLogger --- .../nbm/metrics/impl/eventlogger/sentry/SentryEventLogger.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/adito/aditoweb/nbm/metrics/impl/eventlogger/sentry/SentryEventLogger.java b/src/main/java/de/adito/aditoweb/nbm/metrics/impl/eventlogger/sentry/SentryEventLogger.java index c4d7cd1..f4ca3ea 100644 --- a/src/main/java/de/adito/aditoweb/nbm/metrics/impl/eventlogger/sentry/SentryEventLogger.java +++ b/src/main/java/de/adito/aditoweb/nbm/metrics/impl/eventlogger/sentry/SentryEventLogger.java @@ -452,7 +452,7 @@ public UserFeedbackEvent() /** * Appends random stacktrace elements to the given element array, - * so Sentry is forced to not combining those exceptions + * so Sentry is forced to not combine those exceptions * * @param pOriginalElements Original elements * @return the elements, with the appended generated ones