mirror of
https://github.com/fankes/termux-app.git
synced 2025-09-06 18:55:31 +08:00
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.
492 lines
17 KiB
Java
492 lines
17 KiB
Java
package com.termux.shared.logger;
|
|
|
|
import android.content.Context;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.util.Log;
|
|
import android.widget.Toast;
|
|
|
|
import com.termux.shared.R;
|
|
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;
|
|
|
|
public class Logger {
|
|
|
|
public static final String DEFAULT_LOG_TAG = TermuxConstants.TERMUX_APP_NAME;
|
|
|
|
public static final int LOG_LEVEL_OFF = 0; // log nothing
|
|
public static final int LOG_LEVEL_NORMAL = 1; // start logging error, warn and info messages and stacktraces
|
|
public static final int LOG_LEVEL_DEBUG = 2; // start logging debug messages
|
|
public static final int LOG_LEVEL_VERBOSE = 3; // start logging verbose messages
|
|
|
|
public static final int DEFAULT_LOG_LEVEL = LOG_LEVEL_NORMAL;
|
|
private static int CURRENT_LOG_LEVEL = DEFAULT_LOG_LEVEL;
|
|
|
|
/**
|
|
* 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
|
|
|
|
|
|
|
|
public static void logMessage(int logLevel, String tag, String message) {
|
|
if (logLevel == Log.ERROR && CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL)
|
|
Log.e(getFullTag(tag), message);
|
|
else if (logLevel == Log.WARN && CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL)
|
|
Log.w(getFullTag(tag), message);
|
|
else if (logLevel == Log.INFO && CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL)
|
|
Log.i(getFullTag(tag), message);
|
|
else if (logLevel == Log.DEBUG && CURRENT_LOG_LEVEL >= LOG_LEVEL_DEBUG)
|
|
Log.d(getFullTag(tag), message);
|
|
else if (logLevel == Log.VERBOSE && CURRENT_LOG_LEVEL >= LOG_LEVEL_VERBOSE)
|
|
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) {
|
|
logMessage(Log.ERROR, tag, message);
|
|
}
|
|
|
|
public static void logError(String message) {
|
|
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) {
|
|
logMessage(Log.WARN, tag, message);
|
|
}
|
|
|
|
public static void logWarn(String message) {
|
|
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) {
|
|
logMessage(Log.INFO, tag, message);
|
|
}
|
|
|
|
public static void logInfo(String message) {
|
|
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) {
|
|
logMessage(Log.DEBUG, tag, message);
|
|
}
|
|
|
|
public static void logDebug(String message) {
|
|
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) {
|
|
logMessage(Log.VERBOSE, tag, message);
|
|
}
|
|
|
|
public static void logVerbose(String message) {
|
|
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) {
|
|
if (context == null) return;
|
|
|
|
if (CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL) {
|
|
logError(tag, message);
|
|
showToast(context, message, true);
|
|
}
|
|
}
|
|
|
|
public static void logErrorAndShowToast(Context context, String message) {
|
|
logErrorAndShowToast(context, DEFAULT_LOG_TAG, message);
|
|
}
|
|
|
|
|
|
|
|
public static void logDebugAndShowToast(Context context, String tag, String message) {
|
|
if (context == null) return;
|
|
|
|
if (CURRENT_LOG_LEVEL >= LOG_LEVEL_DEBUG) {
|
|
logDebug(tag, message);
|
|
showToast(context, message, true);
|
|
}
|
|
}
|
|
|
|
public static void logDebugAndShowToast(Context context, String message) {
|
|
logDebugAndShowToast(context, DEFAULT_LOG_TAG, message);
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
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, 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, 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, true);
|
|
}
|
|
|
|
|
|
|
|
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, getSuppressed);
|
|
else if (throwable == null)
|
|
return message;
|
|
else
|
|
return getStackTraceString(throwable, getSuppressed);
|
|
}
|
|
|
|
|
|
|
|
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 && (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, 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) {
|
|
if (throwable == null) return null;
|
|
|
|
String stackTraceString = null;
|
|
|
|
try {
|
|
StringWriter errors = new StringWriter();
|
|
PrintWriter pw = new PrintWriter(errors);
|
|
throwable.printStackTrace(pw);
|
|
pw.close();
|
|
stackTraceString = errors.toString();
|
|
errors.close();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
|
|
return stackTraceString;
|
|
}
|
|
|
|
|
|
|
|
public static String[] getStackTracesStringArray(Throwable throwable, boolean getSuppressed) {
|
|
return getStackTracesStringArray(Collections.singletonList(throwable), getSuppressed);
|
|
}
|
|
|
|
public static String[] getStackTracesStringArray(Throwable throwable) {
|
|
return getStackTracesStringArray(Collections.singletonList(throwable), true);
|
|
}
|
|
|
|
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);
|
|
|
|
if (stackTraceStringArray == null || stackTraceStringArray.length == 0) {
|
|
stackTracesString.append(" -");
|
|
} else {
|
|
for (int i = 0; i != stackTraceStringArray.length; i++) {
|
|
if (stackTraceStringArray.length > 1)
|
|
stackTracesString.append("\n\nStacktrace ").append(i + 1);
|
|
|
|
stackTracesString.append("\n```\n").append(stackTraceStringArray[i]).append("\n```\n");
|
|
}
|
|
}
|
|
|
|
return stackTracesString.toString();
|
|
}
|
|
|
|
public static String getStackTracesMarkdownString(String label, String[] stackTraceStringArray) {
|
|
if (label == null) label = "StackTraces";
|
|
StringBuilder stackTracesString = new StringBuilder("### " + label);
|
|
|
|
if (stackTraceStringArray == null || stackTraceStringArray.length == 0) {
|
|
stackTracesString.append("\n\n`-`");
|
|
} else {
|
|
for (int i = 0; i != stackTraceStringArray.length; i++) {
|
|
if (stackTraceStringArray.length > 1)
|
|
stackTracesString.append("\n\n\n#### Stacktrace ").append(i + 1);
|
|
|
|
stackTracesString.append("\n\n```\n").append(stackTraceStringArray[i]).append("\n```");
|
|
}
|
|
}
|
|
|
|
stackTracesString.append("\n##\n");
|
|
|
|
return stackTracesString.toString();
|
|
}
|
|
|
|
public static String getSingleLineLogStringEntry(String label, Object object, String def) {
|
|
if (object != null)
|
|
return label + ": `" + object + "`";
|
|
else
|
|
return label + ": " + def;
|
|
}
|
|
|
|
public static String getMultiLineLogStringEntry(String label, Object object, String def) {
|
|
if (object != null)
|
|
return label + ":\n```\n" + object + "\n```\n";
|
|
else
|
|
return label + ": " + def;
|
|
}
|
|
|
|
|
|
|
|
public static void showToast(final Context context, final String toastText, boolean longDuration) {
|
|
if (context == null) return;
|
|
|
|
new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(context, toastText, longDuration ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show());
|
|
}
|
|
|
|
|
|
|
|
public static CharSequence[] getLogLevelsArray() {
|
|
return new CharSequence[]{
|
|
String.valueOf(LOG_LEVEL_OFF),
|
|
String.valueOf(LOG_LEVEL_NORMAL),
|
|
String.valueOf(LOG_LEVEL_DEBUG),
|
|
String.valueOf(LOG_LEVEL_VERBOSE)
|
|
};
|
|
}
|
|
|
|
public static CharSequence[] getLogLevelLabelsArray(Context context, CharSequence[] logLevels, boolean addDefaultTag) {
|
|
if (logLevels == null) return null;
|
|
|
|
CharSequence[] logLevelLabels = new CharSequence[logLevels.length];
|
|
|
|
for(int i=0; i<logLevels.length; i++) {
|
|
logLevelLabels[i] = getLogLevelLabel(context, Integer.parseInt(logLevels[i].toString()), addDefaultTag);
|
|
}
|
|
|
|
return logLevelLabels;
|
|
}
|
|
|
|
public static String getLogLevelLabel(final Context context, final int logLevel, final boolean addDefaultTag) {
|
|
String logLabel;
|
|
switch (logLevel) {
|
|
case LOG_LEVEL_OFF: logLabel = context.getString(R.string.log_level_off); break;
|
|
case LOG_LEVEL_NORMAL: logLabel = context.getString(R.string.log_level_normal); break;
|
|
case LOG_LEVEL_DEBUG: logLabel = context.getString(R.string.log_level_debug); break;
|
|
case LOG_LEVEL_VERBOSE: logLabel = context.getString(R.string.log_level_verbose); break;
|
|
default: logLabel = context.getString(R.string.log_level_unknown); break;
|
|
}
|
|
|
|
if (addDefaultTag && logLevel == DEFAULT_LOG_LEVEL)
|
|
return logLabel + " (default)";
|
|
else
|
|
return logLabel;
|
|
}
|
|
|
|
|
|
|
|
public static int getLogLevel() {
|
|
return CURRENT_LOG_LEVEL;
|
|
}
|
|
|
|
public static int setLogLevel(Context context, int logLevel) {
|
|
if (logLevel >= LOG_LEVEL_OFF && logLevel <= LOG_LEVEL_VERBOSE)
|
|
CURRENT_LOG_LEVEL = logLevel;
|
|
else
|
|
CURRENT_LOG_LEVEL = DEFAULT_LOG_LEVEL;
|
|
|
|
if (context != null)
|
|
showToast(context, context.getString(R.string.log_level_value, getLogLevelLabel(context, CURRENT_LOG_LEVEL, false)),true);
|
|
|
|
return CURRENT_LOG_LEVEL;
|
|
}
|
|
|
|
public static String getFullTag(String tag) {
|
|
if (DEFAULT_LOG_TAG.equals(tag))
|
|
return tag;
|
|
else
|
|
return DEFAULT_LOG_TAG + ":" + tag;
|
|
}
|
|
|
|
}
|