From 724a6b6df97b7cbac8f85945a375b0b4cff205f8 Mon Sep 17 00:00:00 2001 From: Tiago Bagni Date: Sat, 4 May 2024 13:46:46 -0500 Subject: [PATCH] Improve logger and dump local logs when a crash happens --- .../logviewer/LogViewerApplication.java | 7 +++ .../com/tibagni/logviewer/logger/Logger.java | 55 +++++++++++++++++-- .../tibagni/logviewer/rc/LogLevelConfig.java | 2 +- 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/tibagni/logviewer/LogViewerApplication.java b/src/main/java/com/tibagni/logviewer/LogViewerApplication.java index 5cd09b7..0d1b66e 100644 --- a/src/main/java/com/tibagni/logviewer/LogViewerApplication.java +++ b/src/main/java/com/tibagni/logviewer/LogViewerApplication.java @@ -20,7 +20,9 @@ import java.io.*; import java.nio.file.Path; import java.nio.file.Paths; +import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Date; import java.util.Set; import java.util.stream.Collectors; @@ -55,8 +57,13 @@ 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); + SimpleDateFormat formatter = new SimpleDateFormat("dd-MM HH:mm:ss.SSS"); try (PrintWriter pw = new PrintWriter(new FileWriter(filePath.toFile()))) { + pw.println("Exception happened on thread " + t.getId() + " (" + t.getName() + ") at " + + formatter.format(new Date())); e.printStackTrace(pw); + pw.println("\n---------------- Previous logs before the exception:"); + Logger.dump(pw); } catch (IOException ex) { ex.printStackTrace(); } diff --git a/src/main/java/com/tibagni/logviewer/logger/Logger.java b/src/main/java/com/tibagni/logviewer/logger/Logger.java index 54eac0d..6edff4d 100644 --- a/src/main/java/com/tibagni/logviewer/logger/Logger.java +++ b/src/main/java/com/tibagni/logviewer/logger/Logger.java @@ -3,10 +3,17 @@ import com.tibagni.logviewer.rc.LogLevelConfig; import java.io.PrintStream; +import java.io.PrintWriter; +import java.text.DecimalFormat; import java.text.SimpleDateFormat; +import java.util.ArrayDeque; import java.util.Date; public class Logger { + private static final int MAX_LOGS_CACHE = 500; + private static final ArrayDeque logsCache = new ArrayDeque<>(MAX_LOGS_CACHE); + private static final Object logsCacheLock = new Object(); + private static LogLevelConfig.Level logLevel = LogLevelConfig.DEFAULT_LEVEL; private Logger() {} @@ -74,20 +81,60 @@ public static void error(String message, Throwable throwable) { } } + // Use same format as Android so we can use LogViewer to analyze its own logs private static void log(LogLevelConfig.Level level, String message) { - // 04-02 13:16:34.662 message - String pattern = "dd-MM HH:mm:ss.S"; + // 04-02 13:16:34.662 + String pattern = "dd-MM HH:mm:ss.SSS"; SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern); String date = simpleDateFormat.format(new Date()); - String levelIndicator = level.name().substring(0,1).toUpperCase(); + String levelIndicator = level.name().substring(0, 1).toUpperCase(); + DecimalFormat tidFormat = new DecimalFormat("000"); + String tid = tidFormat.format(Thread.currentThread().getId()); + String pid = "001"; // TODO use the real PID one day if needed - String logMessage = date + " " + levelIndicator + " " + message; + String logMessage = date + " " + pid + " " + tid + " " + levelIndicator + " " + getCallingClassName() + ": " + message; if (level == LogLevelConfig.Level.WARNING || level == LogLevelConfig.Level.ERROR) { errorStream.println(logMessage); } else { debugStream.println(logMessage); } + addLogToCache(logMessage); + } + + private static String getCallingClassName() { + StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); + if (stackTraceElements.length > 4) { + // Index 0 is getStackTrace(), 1 is getCallingClassName(), 2 is log(), 3 is the caller (which is inside + // the Logger class), 4 is the actual caller + StackTraceElement caller = stackTraceElements[4]; + String className = caller.getClassName(); + int lastDotIndex = className.lastIndexOf('.'); + if (lastDotIndex != -1 && lastDotIndex < className.length() - 1) { + return className.substring(lastDotIndex + 1); + } + return className; // If there is no package name + } + return "Unknown"; + } + + private static void addLogToCache(String log) { + synchronized (logsCacheLock) { + if (logsCache.size() >= MAX_LOGS_CACHE) { + // We reached the maximum size, drop the first log before inserting the new one + logsCache.pollFirst(); + } + + logsCache.add(log); + } + } + + public static void dump(PrintWriter pw) { + synchronized (logsCacheLock) { + while (!logsCache.isEmpty()) { + pw.println(logsCache.removeFirst()); + } + } } static boolean isLoggable(LogLevelConfig.Level level) { diff --git a/src/main/java/com/tibagni/logviewer/rc/LogLevelConfig.java b/src/main/java/com/tibagni/logviewer/rc/LogLevelConfig.java index c8b6b2a..4eb1199 100644 --- a/src/main/java/com/tibagni/logviewer/rc/LogLevelConfig.java +++ b/src/main/java/com/tibagni/logviewer/rc/LogLevelConfig.java @@ -10,7 +10,7 @@ public enum Level { WARNING, ERROR } - public static final Level DEFAULT_LEVEL = Level.DEBUG; + public static final Level DEFAULT_LEVEL = Level.WARNING; private final Level levelConfig; public LogLevelConfig(String value) {