Fix suppressed exceptions not being logged and long logcat message being truncated

If an exception is thrown, the exception message might not contain the full errors. Individual failures may get added to suppressed throwables which can be extracted from the exception object by calling `Throwable[] getSuppressed()`. So just logging the exception message and stacktrace may not be enough, the suppressed throwables need to be logged as well.

The Logger class will now log the suppressed throwables as well if they are found in the exception.

This was mainly a concern for FileUtils where guava MoreUtils library was used to delete directories but exceptions weren't being fully logged on failures, like bootstrap failures, so user wouldn't know what really caused the failure.

https://github.com/google/guava/blob/v30.1.1/guava/src/com/google/common/io/MoreFiles.java#L775

The FileUtils will be fixed in a future commit.

This also adds support with "log*Extended()" functions so that logcat entries longer than LOGGER_ENTRY_MAX_PAYLOAD do not get truncated by android. This is done by splitting the log message into multiple messages if the limit is crossed. This is specially necessary for logging long stacktraces, suppressed throwables and errmsg of ExecutionCommand, etc.
This commit is contained in:
agnostic-apollo 2021-06-26 06:01:06 +05:00
parent 80b495e50b
commit 679e0de044
1 changed files with 187 additions and 26 deletions

View File

@ -12,6 +12,7 @@ import com.termux.shared.termux.TermuxConstants;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -27,7 +28,25 @@ public class Logger {
public static final int DEFAULT_LOG_LEVEL = LOG_LEVEL_NORMAL;
private static int CURRENT_LOG_LEVEL = DEFAULT_LOG_LEVEL;
public static final int LOGGER_ENTRY_SIZE_LIMIT_IN_BYTES = 4 * 1024; // 4KB
/**
* The maximum size of the log entry payload that can be written to the logger. An attempt to
* write more than this amount will result in a truncated log entry.
*
* The limit is 4068 but this includes log tag and log level prefix "D/" before log tag and ": "
* suffix after it.
*
* #define LOGGER_ENTRY_MAX_PAYLOAD 4068
* https://cs.android.com/android/_/android/platform/system/core/+/android10-release:liblog/include/log/log_read.h;l=127
*/
public static final int LOGGER_ENTRY_MAX_PAYLOAD = 4068; // 4068 bytes
/**
* The maximum safe size of the log entry payload that can be written to the logger, based on
* {@link #LOGGER_ENTRY_MAX_PAYLOAD}. Using 4000 as a safe limit to give log tag and its
* prefix/suffix max 68 characters for itself. Use "log*Extended()" functions to use max possible
* limit if tag is already known.
*/
public static final int LOGGER_ENTRY_MAX_SAFE_PAYLOAD = 4000; // 4000 bytes
@ -44,6 +63,40 @@ public class Logger {
Log.v(getFullTag(tag), message);
}
public static void logExtendedMessage(int logLevel, String tag, String message) {
if (message == null) return;
int cutOffIndex;
int nextNewlineIndex;
String prefix = "";
// -8 for prefix "(xx/xx)" (max 99 sections), - log tag length, -4 for log tag prefix "D/" and suffix ": "
int maxEntrySize = LOGGER_ENTRY_MAX_PAYLOAD - 8 - getFullTag(tag).length() - 4;
List<String> messagesList = new ArrayList<>();
while(!message.isEmpty()) {
if (message.length() > maxEntrySize) {
cutOffIndex = maxEntrySize;
nextNewlineIndex = message.lastIndexOf('\n', cutOffIndex);
if (nextNewlineIndex != -1) {
cutOffIndex = nextNewlineIndex + 1;
}
messagesList.add(message.substring(0, cutOffIndex));
message = message.substring(cutOffIndex);
} else {
messagesList.add(message);
break;
}
}
for(int i=0; i<messagesList.size(); i++) {
if (messagesList.size() > 1)
prefix = "(" + (i + 1) + "/" + messagesList.size() + ")\n";
logMessage(logLevel, tag, prefix + messagesList.get(i));
}
}
public static void logError(String tag, String message) {
@ -54,6 +107,14 @@ public class Logger {
logMessage(Log.ERROR, DEFAULT_LOG_TAG, message);
}
public static void logErrorExtended(String tag, String message) {
logExtendedMessage(Log.ERROR, tag, message);
}
public static void logErrorExtended(String message) {
logExtendedMessage(Log.ERROR, DEFAULT_LOG_TAG, message);
}
public static void logWarn(String tag, String message) {
@ -64,6 +125,14 @@ public class Logger {
logMessage(Log.WARN, DEFAULT_LOG_TAG, message);
}
public static void logWarnExtended(String tag, String message) {
logExtendedMessage(Log.WARN, tag, message);
}
public static void logWarnExtended(String message) {
logExtendedMessage(Log.WARN, DEFAULT_LOG_TAG, message);
}
public static void logInfo(String tag, String message) {
@ -74,6 +143,14 @@ public class Logger {
logMessage(Log.INFO, DEFAULT_LOG_TAG, message);
}
public static void logInfoExtended(String tag, String message) {
logExtendedMessage(Log.INFO, tag, message);
}
public static void logInfoExtended(String message) {
logExtendedMessage(Log.INFO, DEFAULT_LOG_TAG, message);
}
public static void logDebug(String tag, String message) {
@ -84,6 +161,14 @@ public class Logger {
logMessage(Log.DEBUG, DEFAULT_LOG_TAG, message);
}
public static void logDebugExtended(String tag, String message) {
logExtendedMessage(Log.DEBUG, tag, message);
}
public static void logDebugExtended(String message) {
logExtendedMessage(Log.DEBUG, DEFAULT_LOG_TAG, message);
}
public static void logVerbose(String tag, String message) {
@ -94,6 +179,14 @@ public class Logger {
logMessage(Log.VERBOSE, DEFAULT_LOG_TAG, message);
}
public static void logVerboseExtended(String tag, String message) {
logExtendedMessage(Log.VERBOSE, tag, message);
}
public static void logVerboseExtended(String message) {
logExtendedMessage(Log.VERBOSE, DEFAULT_LOG_TAG, message);
}
public static void logErrorAndShowToast(Context context, String tag, String message) {
@ -126,48 +219,103 @@ public class Logger {
public static void logStackTraceWithMessage(String tag, String message, Throwable throwable, boolean getSuppressed) {
Logger.logErrorExtended(tag, getMessageAndStackTraceString(message, throwable, getSuppressed));
}
public static void logStackTraceWithMessage(String tag, String message, Throwable throwable) {
if (CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL)
Log.e(getFullTag(tag), getMessageAndStackTraceString(message, throwable));
logStackTraceWithMessage(tag, message, throwable, true);
}
public static void logStackTraceWithMessage(String message, Throwable throwable, boolean getSuppressed) {
logStackTraceWithMessage(DEFAULT_LOG_TAG, message, throwable, getSuppressed);
}
public static void logStackTraceWithMessage(String message, Throwable throwable) {
logStackTraceWithMessage(DEFAULT_LOG_TAG, message, throwable);
logStackTraceWithMessage(DEFAULT_LOG_TAG, message, throwable, true);
}
public static void logStackTrace(String tag, Throwable throwable, boolean getSuppressed) {
logStackTraceWithMessage(tag, null, throwable, getSuppressed);
}
public static void logStackTrace(String tag, Throwable throwable) {
logStackTraceWithMessage(tag, null, throwable);
logStackTraceWithMessage(tag, null, throwable, true);
}
public static void logStackTrace(Throwable throwable, boolean getSuppressed) {
logStackTraceWithMessage(DEFAULT_LOG_TAG, null, throwable, getSuppressed);
}
public static void logStackTrace(Throwable throwable) {
logStackTraceWithMessage(DEFAULT_LOG_TAG, null, throwable);
logStackTraceWithMessage(DEFAULT_LOG_TAG, null, throwable, true);
}
public static void logStackTracesWithMessage(String tag, String message, List<Throwable> throwableList) {
if (CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL)
Log.e(getFullTag(tag), getMessageAndStackTracesString(message, throwableList));
public static void logStackTracesWithMessage(String tag, String message, List<Throwable> throwablesList, boolean getSuppressed) {
Logger.logErrorExtended(tag, getMessageAndStackTracesString(message, throwablesList, getSuppressed));
}
public static void logStackTracesWithMessage(String tag, String message, List<Throwable> throwablesList) {
Logger.logErrorExtended(tag, getMessageAndStackTracesString(message, throwablesList, true));
}
public static String getMessageAndStackTraceString(String message, Throwable throwable) {
return getMessageAndStackTraceString(message, throwable, true);
}
public static String getMessageAndStackTraceString(String message, Throwable throwable, boolean getSuppressed) {
if (message == null && throwable == null)
return null;
else if (message != null && throwable != null)
return message + ":\n" + getStackTraceString(throwable);
return message + ":\n" + getStackTraceString(throwable, getSuppressed);
else if (throwable == null)
return message;
else
return getStackTraceString(throwable);
return getStackTraceString(throwable, getSuppressed);
}
public static String getMessageAndStackTracesString(String message, List<Throwable> throwableList) {
if (message == null && (throwableList == null || throwableList.size() == 0))
public static String getMessageAndStackTracesString(String message, List<Throwable> throwablesList) {
return getMessageAndStackTracesString(message, throwablesList, true);
}
public static String getMessageAndStackTracesString(String message, List<Throwable> throwablesList, boolean getSuppressed) {
if (message == null && (throwablesList == null || throwablesList.size() == 0))
return null;
else if (message != null && (throwableList != null && throwableList.size() != 0))
return message + ":\n" + getStackTracesString(null, getStackTraceStringArray(throwableList));
else if (throwableList == null || throwableList.size() == 0)
else if (message != null && (throwablesList != null && throwablesList.size() != 0))
return message + ":\n" + getStackTracesString(null, getStackTracesStringArray(throwablesList, getSuppressed));
else if (throwablesList == null || throwablesList.size() == 0)
return message;
else
return getStackTracesString(null, getStackTraceStringArray(throwableList));
return getStackTracesString(null, getStackTracesStringArray(throwablesList, getSuppressed));
}
public static String getStackTraceString(Throwable throwable, boolean getSuppressed) {
if (throwable == null) return null;
StringBuilder stackTraceString = new StringBuilder();
stackTraceString.append(getStackTraceString(throwable));
if (getSuppressed) {
Throwable[] suppressedThrowablesArray = throwable.getSuppressed();
if (suppressedThrowablesArray != null && suppressedThrowablesArray.length > 0) {
for (Throwable suppressedThrowable : suppressedThrowablesArray) {
if (suppressedThrowable == null) continue;
stackTraceString.append("\n\n").append(getStackTraceString(suppressedThrowable));
}
}
}
return stackTraceString.toString();
}
public static String getStackTraceString(Throwable throwable) {
@ -182,27 +330,40 @@ public class Logger {
pw.close();
stackTraceString = errors.toString();
errors.close();
} catch (IOException e1) {
e1.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return stackTraceString;
}
public static String[] getStackTraceStringArray(Throwable throwable) {
return getStackTraceStringArray(Collections.singletonList(throwable));
public static String[] getStackTracesStringArray(Throwable throwable, boolean getSuppressed) {
return getStackTracesStringArray(Collections.singletonList(throwable), getSuppressed);
}
public static String[] getStackTraceStringArray(List<Throwable> throwableList) {
if (throwableList == null) return null;
public static String[] getStackTracesStringArray(Throwable throwable) {
return getStackTracesStringArray(Collections.singletonList(throwable), true);
}
final String[] stackTraceStringArray = new String[throwableList.size()];
for (int i = 0; i < throwableList.size(); i++) {
stackTraceStringArray[i] = getStackTraceString(throwableList.get(i));
public static String[] getStackTracesStringArray(List<Throwable> throwablesList) {
return getStackTracesStringArray(throwablesList, true);
}
public static String[] getStackTracesStringArray(List<Throwable> throwablesList, boolean getSuppressed) {
if (throwablesList == null) return null;
final String[] stackTraceStringArray = new String[throwablesList.size()];
for (int i = 0; i < throwablesList.size(); i++) {
stackTraceStringArray[i] = getStackTraceString(throwablesList.get(i), getSuppressed);
}
return stackTraceStringArray;
}
public static String getStackTracesString(String label, String[] stackTraceStringArray) {
if (label == null) label = "StackTraces:";
StringBuilder stackTracesString = new StringBuilder(label);