From 4b8bc6167ac95c02cd32b63164e79f278b73d815 Mon Sep 17 00:00:00 2001 From: Tiago Bagni Date: Wed, 1 May 2024 17:01:05 -0500 Subject: [PATCH] Save uncaught exceptions based on logviewer RC --- .../logviewer/LogViewerApplication.java | 25 +++++++++++- .../tibagni/logviewer/rc/CrashReportConfig.kt | 10 +++++ .../logviewer/rc/RuntimeConfiguration.java | 25 +++++++----- .../tibagni/logviewer/util/CommonUtils.java | 23 +++++++++++ .../logviewer/rc/RuntimeConfigurationTests.kt | 40 +++++++++++++------ 5 files changed, 99 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/tibagni/logviewer/rc/CrashReportConfig.kt diff --git a/src/main/java/com/tibagni/logviewer/LogViewerApplication.java b/src/main/java/com/tibagni/logviewer/LogViewerApplication.java index b6e3c26..5cd09b7 100644 --- a/src/main/java/com/tibagni/logviewer/LogViewerApplication.java +++ b/src/main/java/com/tibagni/logviewer/LogViewerApplication.java @@ -3,6 +3,7 @@ import com.formdev.flatlaf.util.UIScale; import com.tibagni.logviewer.logger.Logger; import com.tibagni.logviewer.preferences.LogViewerPreferences; +import com.tibagni.logviewer.rc.CrashReportConfig; import com.tibagni.logviewer.rc.LogLevelConfig; import com.tibagni.logviewer.rc.RuntimeConfiguration; import com.tibagni.logviewer.rc.UIScaleConfig; @@ -10,12 +11,15 @@ import com.tibagni.logviewer.updates.ReleaseInfo; import com.tibagni.logviewer.updates.UpdateAvailableDialog; import com.tibagni.logviewer.updates.UpdateManager; +import com.tibagni.logviewer.util.CommonUtils; import com.tibagni.logviewer.util.StringUtils; import com.tibagni.logviewer.util.scaling.UIScaleUtils; import org.jetbrains.annotations.NotNull; import javax.swing.*; -import java.io.File; +import java.io.*; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; @@ -30,6 +34,13 @@ public static void main(String[] args) { UIScaleUtils.initialize(RuntimeConfiguration.getConfig(RuntimeConfiguration.UI_SCALE, UIScaleConfig.class)); Logger.initialize(RuntimeConfiguration.getConfig(RuntimeConfiguration.LOG_LEVEL, LogLevelConfig.class)); + CrashReportConfig crashReportConfig = RuntimeConfiguration.getConfig(RuntimeConfiguration.CRASH_REPORT, + CrashReportConfig.class); + if (crashReportConfig != null && crashReportConfig.getConfigValue()) { + Logger.info("Enabling crash report..."); + configureUncaughtExceptionHandler(); + } + Set initialLogFiles = Arrays .stream(args) .map(File::new) @@ -40,6 +51,18 @@ public static void main(String[] args) { application.start(initialLogFiles); } + private static void configureUncaughtExceptionHandler() { + Thread.setDefaultUncaughtExceptionHandler((t, e) -> { + String fileName = "CRASH-logviewer_" + CommonUtils.calculateStackTraceHash(e) + ".txt"; + Path filePath = Paths.get(System.getProperty("user.home"), fileName); + try (PrintWriter pw = new PrintWriter(new FileWriter(filePath.toFile()))) { + e.printStackTrace(pw); + } catch (IOException ex) { + ex.printStackTrace(); + } + }); + } + private void start(Set initialLogFiles) { startCheckingForUpdates(); initLookAndFeel(); diff --git a/src/main/java/com/tibagni/logviewer/rc/CrashReportConfig.kt b/src/main/java/com/tibagni/logviewer/rc/CrashReportConfig.kt new file mode 100644 index 0000000..87c755b --- /dev/null +++ b/src/main/java/com/tibagni/logviewer/rc/CrashReportConfig.kt @@ -0,0 +1,10 @@ +package com.tibagni.logviewer.rc + +class CrashReportConfig(configValue: String) : Config { + private val state: Boolean + init { + state = configValue.lowercase() == "on" + } + + override fun getConfigValue() = state +} \ No newline at end of file diff --git a/src/main/java/com/tibagni/logviewer/rc/RuntimeConfiguration.java b/src/main/java/com/tibagni/logviewer/rc/RuntimeConfiguration.java index d5ff00d..0f6ffc4 100644 --- a/src/main/java/com/tibagni/logviewer/rc/RuntimeConfiguration.java +++ b/src/main/java/com/tibagni/logviewer/rc/RuntimeConfiguration.java @@ -9,15 +9,17 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; +import java.util.stream.Stream; public class RuntimeConfiguration { private static final Path RC_FILE_PATH = Paths.get(System.getProperty("user.home"), ".logviewer"); private static RuntimeConfiguration instance; - private HashMap runtimeConfigs = new HashMap<>(); + private final HashMap> runtimeConfigs = new HashMap<>(); public static final String UI_SCALE = "uiscale"; public static final String LOG_LEVEL = "loglevel"; + public static final String CRASH_REPORT = "crashreport"; @NotNull static RuntimeConfiguration initializeForTest() { @@ -35,16 +37,16 @@ public static void initialize() { public static T getConfig(String configName, Class type) { if (instance.runtimeConfigs.containsKey(configName)) { - return (T) instance.runtimeConfigs.get(configName); + Object config = instance.runtimeConfigs.get(configName); + if (type.isInstance(config)) { + //noinspection unchecked + return (T) config; + } } return null; } - public static Config getConfig(String configName) { - return getConfig(configName, Config.class); - } - // For test only private RuntimeConfiguration() { } @@ -54,10 +56,8 @@ private RuntimeConfiguration(Path rcFilePath) { return; } - try { - Files.lines(rcFilePath) - .filter(StringUtils::isNotEmpty) - .forEach(this::parseConfig); + try (Stream lines = Files.lines(rcFilePath)) { + lines.filter(StringUtils::isNotEmpty).forEach(this::parseConfig); } catch (IOException e) { Logger.error("Could not read rc file", e); } @@ -74,7 +74,7 @@ void parseConfig(@NotNull String configLine) { String configName = configParts[0].toLowerCase(); String configValue = configParts[1].toLowerCase(); - Config config = null; + Config config = null; switch (configName) { case UI_SCALE: config = new UIScaleConfig(configValue); @@ -82,6 +82,9 @@ void parseConfig(@NotNull String configLine) { case LOG_LEVEL: config = new LogLevelConfig(configValue); break; + case CRASH_REPORT: + config = new CrashReportConfig(configValue); + break; default: Logger.error("Invalid config: " + configName); break; diff --git a/src/main/java/com/tibagni/logviewer/util/CommonUtils.java b/src/main/java/com/tibagni/logviewer/util/CommonUtils.java index bebc121..fd2336e 100644 --- a/src/main/java/com/tibagni/logviewer/util/CommonUtils.java +++ b/src/main/java/com/tibagni/logviewer/util/CommonUtils.java @@ -2,6 +2,8 @@ import com.tibagni.logviewer.logger.Logger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -31,4 +33,25 @@ > List asSortedList(Collection c) { Collections.sort(list); return list; } + + public static String calculateStackTraceHash(Throwable throwable) { + StringBuilder stackTrace = new StringBuilder(); + for (StackTraceElement element : throwable.getStackTrace()) { + stackTrace.append(element.toString()).append("\n"); + } + + byte[] bytes = stackTrace.toString().getBytes(); + + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hashBytes = digest.digest(bytes); + StringBuilder hexString = new StringBuilder(); + for (byte b : hashBytes) { + hexString.append(String.format("%02x", b)); + } + return hexString.toString(); + } catch (NoSuchAlgorithmException ignored) { } + + return "empty"; + } } diff --git a/src/test/java/com/tibagni/logviewer/rc/RuntimeConfigurationTests.kt b/src/test/java/com/tibagni/logviewer/rc/RuntimeConfigurationTests.kt index a0c05bd..b2df8e4 100644 --- a/src/test/java/com/tibagni/logviewer/rc/RuntimeConfigurationTests.kt +++ b/src/test/java/com/tibagni/logviewer/rc/RuntimeConfigurationTests.kt @@ -17,31 +17,45 @@ class RuntimeConfigurationTests { fun testUiScaleConfig() { testRcConfig.parseConfig("uiscale=2") - assertNotNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.UI_SCALE)) - assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.LOG_LEVEL)) + assertNotNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.UI_SCALE, UIScaleConfig::class.java)) + assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.LOG_LEVEL, LogLevelConfig::class.java)) + assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.CRASH_REPORT, CrashReportConfig::class.java)) } @Test fun testLogLevelConfig() { testRcConfig.parseConfig("loglevel=v") - assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.UI_SCALE)) - assertNotNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.LOG_LEVEL)) + assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.UI_SCALE, UIScaleConfig::class.java)) + assertNotNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.LOG_LEVEL, LogLevelConfig::class.java)) + assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.CRASH_REPORT, CrashReportConfig::class.java)) + } + + @Test + fun testCrashReportConfig() { + testRcConfig.parseConfig("crashreport=on") + + assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.UI_SCALE, UIScaleConfig::class.java)) + assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.LOG_LEVEL, LogLevelConfig::class.java)) + assertNotNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.CRASH_REPORT, CrashReportConfig::class.java)) } @Test fun testAllConfig() { testRcConfig.parseConfig("loglevel=verbose") testRcConfig.parseConfig("uiscale=5") + testRcConfig.parseConfig("crashreport=ON") - assertNotNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.UI_SCALE)) - assertNotNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.LOG_LEVEL)) + assertNotNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.UI_SCALE, UIScaleConfig::class.java)) + assertNotNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.LOG_LEVEL, LogLevelConfig::class.java)) + assertNotNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.CRASH_REPORT, CrashReportConfig::class.java)) } @Test fun testNoConfig() { - assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.UI_SCALE)) - assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.LOG_LEVEL)) + assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.UI_SCALE, UIScaleConfig::class.java)) + assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.LOG_LEVEL, LogLevelConfig::class.java)) + assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.CRASH_REPORT, CrashReportConfig::class.java)) } @Test @@ -49,8 +63,9 @@ class RuntimeConfigurationTests { testRcConfig.parseConfig("invalidkey=dddddd") testRcConfig.parseConfig("nonexistentconfig=eeee") - assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.UI_SCALE)) - assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.LOG_LEVEL)) + assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.UI_SCALE, UIScaleConfig::class.java)) + assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.LOG_LEVEL, LogLevelConfig::class.java)) + assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.CRASH_REPORT, CrashReportConfig::class.java)) } @Test @@ -60,7 +75,8 @@ class RuntimeConfigurationTests { testRcConfig.parseConfig("") testRcConfig.parseConfig(" ") - assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.UI_SCALE)) - assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.LOG_LEVEL)) + assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.UI_SCALE, UIScaleConfig::class.java)) + assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.LOG_LEVEL, LogLevelConfig::class.java)) + assertNull(RuntimeConfiguration.getConfig(RuntimeConfiguration.CRASH_REPORT, CrashReportConfig::class.java)) } } \ No newline at end of file