Skip to content

Commit

Permalink
Improve logger and dump local logs when a crash happens
Browse files Browse the repository at this point in the history
  • Loading branch information
tibagni committed May 4, 2024
1 parent 4b8bc61 commit 724a6b6
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 5 deletions.
7 changes: 7 additions & 0 deletions src/main/java/com/tibagni/logviewer/LogViewerApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
}
Expand Down
55 changes: 51 additions & 4 deletions src/main/java/com/tibagni/logviewer/logger/Logger.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> logsCache = new ArrayDeque<>(MAX_LOGS_CACHE);
private static final Object logsCacheLock = new Object();

private static LogLevelConfig.Level logLevel = LogLevelConfig.DEFAULT_LEVEL;

private Logger() {}
Expand Down Expand Up @@ -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 <pid> <tid> <level> <message>
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) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/tibagni/logviewer/rc/LogLevelConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 724a6b6

Please sign in to comment.