termux-app
502 строки · 17.6 Кб
1package com.termux.shared.logger;2
3import android.content.Context;4import android.os.Handler;5import android.os.Looper;6import android.util.Log;7import android.widget.Toast;8
9import androidx.annotation.NonNull;10
11import com.termux.shared.R;12import com.termux.shared.data.DataUtils;13
14import java.io.IOException;15import java.io.PrintWriter;16import java.io.StringWriter;17import java.util.ArrayList;18import java.util.Collections;19import java.util.List;20
21public class Logger {22
23private static String DEFAULT_LOG_TAG = "Logger";24
25public static final int LOG_LEVEL_OFF = 0; // log nothing26public static final int LOG_LEVEL_NORMAL = 1; // start logging error, warn and info messages and stacktraces27public static final int LOG_LEVEL_DEBUG = 2; // start logging debug messages28public static final int LOG_LEVEL_VERBOSE = 3; // start logging verbose messages29
30public static final int DEFAULT_LOG_LEVEL = LOG_LEVEL_NORMAL;31public static final int MAX_LOG_LEVEL = LOG_LEVEL_VERBOSE;32private static int CURRENT_LOG_LEVEL = DEFAULT_LOG_LEVEL;33
34/**35* The maximum size of the log entry payload that can be written to the logger. An attempt to
36* write more than this amount will result in a truncated log entry.
37*
38* The limit is 4068 but this includes log tag and log level prefix "D/" before log tag and ": "
39* suffix after it.
40*
41* #define LOGGER_ENTRY_MAX_PAYLOAD 4068
42* https://cs.android.com/android/_/android/platform/system/core/+/android10-release:liblog/include/log/log_read.h;l=127
43*/
44public static final int LOGGER_ENTRY_MAX_PAYLOAD = 4068; // 4068 bytes45
46/**47* The maximum safe size of the log entry payload that can be written to the logger, based on
48* {@link #LOGGER_ENTRY_MAX_PAYLOAD}. Using 4000 as a safe limit to give log tag and its
49* prefix/suffix max 68 characters for itself. Use "log*Extended()" functions to use max possible
50* limit if tag is already known.
51*/
52public static final int LOGGER_ENTRY_MAX_SAFE_PAYLOAD = 4000; // 4000 bytes53
54
55
56public static void logMessage(int logPriority, String tag, String message) {57if (logPriority == Log.ERROR && CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL)58Log.e(getFullTag(tag), message);59else if (logPriority == Log.WARN && CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL)60Log.w(getFullTag(tag), message);61else if (logPriority == Log.INFO && CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL)62Log.i(getFullTag(tag), message);63else if (logPriority == Log.DEBUG && CURRENT_LOG_LEVEL >= LOG_LEVEL_DEBUG)64Log.d(getFullTag(tag), message);65else if (logPriority == Log.VERBOSE && CURRENT_LOG_LEVEL >= LOG_LEVEL_VERBOSE)66Log.v(getFullTag(tag), message);67}68
69public static void logExtendedMessage(int logLevel, String tag, String message) {70if (message == null) return;71
72int cutOffIndex;73int nextNewlineIndex;74String prefix = "";75
76// -8 for prefix "(xx/xx)" (max 99 sections), - log tag length, -4 for log tag prefix "D/" and suffix ": "77int maxEntrySize = LOGGER_ENTRY_MAX_PAYLOAD - 8 - getFullTag(tag).length() - 4;78
79List<String> messagesList = new ArrayList<>();80
81while(!message.isEmpty()) {82if (message.length() > maxEntrySize) {83cutOffIndex = maxEntrySize;84nextNewlineIndex = message.lastIndexOf('\n', cutOffIndex);85if (nextNewlineIndex != -1) {86cutOffIndex = nextNewlineIndex + 1;87}88messagesList.add(message.substring(0, cutOffIndex));89message = message.substring(cutOffIndex);90} else {91messagesList.add(message);92break;93}94}95
96for(int i=0; i<messagesList.size(); i++) {97if (messagesList.size() > 1)98prefix = "(" + (i + 1) + "/" + messagesList.size() + ")\n";99logMessage(logLevel, tag, prefix + messagesList.get(i));100}101}102
103
104
105public static void logError(String tag, String message) {106logMessage(Log.ERROR, tag, message);107}108
109public static void logError(String message) {110logMessage(Log.ERROR, DEFAULT_LOG_TAG, message);111}112
113public static void logErrorExtended(String tag, String message) {114logExtendedMessage(Log.ERROR, tag, message);115}116
117public static void logErrorExtended(String message) {118logExtendedMessage(Log.ERROR, DEFAULT_LOG_TAG, message);119}120
121
122
123public static void logErrorPrivate(String tag, String message) {124if (CURRENT_LOG_LEVEL >= LOG_LEVEL_DEBUG)125logMessage(Log.ERROR, tag, message);126}127
128public static void logErrorPrivate(String message) {129if (CURRENT_LOG_LEVEL >= LOG_LEVEL_DEBUG)130logMessage(Log.ERROR, DEFAULT_LOG_TAG, message);131}132
133public static void logErrorPrivateExtended(String tag, String message) {134if (CURRENT_LOG_LEVEL >= LOG_LEVEL_DEBUG)135logExtendedMessage(Log.ERROR, tag, message);136}137
138public static void logErrorPrivateExtended(String message) {139if (CURRENT_LOG_LEVEL >= LOG_LEVEL_DEBUG)140logExtendedMessage(Log.ERROR, DEFAULT_LOG_TAG, message);141}142
143
144
145public static void logWarn(String tag, String message) {146logMessage(Log.WARN, tag, message);147}148
149public static void logWarn(String message) {150logMessage(Log.WARN, DEFAULT_LOG_TAG, message);151}152
153public static void logWarnExtended(String tag, String message) {154logExtendedMessage(Log.WARN, tag, message);155}156
157public static void logWarnExtended(String message) {158logExtendedMessage(Log.WARN, DEFAULT_LOG_TAG, message);159}160
161
162
163public static void logInfo(String tag, String message) {164logMessage(Log.INFO, tag, message);165}166
167public static void logInfo(String message) {168logMessage(Log.INFO, DEFAULT_LOG_TAG, message);169}170
171public static void logInfoExtended(String tag, String message) {172logExtendedMessage(Log.INFO, tag, message);173}174
175public static void logInfoExtended(String message) {176logExtendedMessage(Log.INFO, DEFAULT_LOG_TAG, message);177}178
179
180
181public static void logDebug(String tag, String message) {182logMessage(Log.DEBUG, tag, message);183}184
185public static void logDebug(String message) {186logMessage(Log.DEBUG, DEFAULT_LOG_TAG, message);187}188
189public static void logDebugExtended(String tag, String message) {190logExtendedMessage(Log.DEBUG, tag, message);191}192
193public static void logDebugExtended(String message) {194logExtendedMessage(Log.DEBUG, DEFAULT_LOG_TAG, message);195}196
197
198
199public static void logVerbose(String tag, String message) {200logMessage(Log.VERBOSE, tag, message);201}202
203public static void logVerbose(String message) {204logMessage(Log.VERBOSE, DEFAULT_LOG_TAG, message);205}206
207public static void logVerboseExtended(String tag, String message) {208logExtendedMessage(Log.VERBOSE, tag, message);209}210
211public static void logVerboseExtended(String message) {212logExtendedMessage(Log.VERBOSE, DEFAULT_LOG_TAG, message);213}214
215public static void logVerboseForce(String tag, String message) {216Log.v(tag, message);217}218
219
220
221public static void logInfoAndShowToast(Context context, String tag, String message) {222if (CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL) {223logInfo(tag, message);224showToast(context, message, true);225}226}227
228public static void logInfoAndShowToast(Context context, String message) {229logInfoAndShowToast(context, DEFAULT_LOG_TAG, message);230}231
232
233
234public static void logErrorAndShowToast(Context context, String tag, String message) {235if (CURRENT_LOG_LEVEL >= LOG_LEVEL_NORMAL) {236logError(tag, message);237showToast(context, message, true);238}239}240
241public static void logErrorAndShowToast(Context context, String message) {242logErrorAndShowToast(context, DEFAULT_LOG_TAG, message);243}244
245
246
247public static void logDebugAndShowToast(Context context, String tag, String message) {248if (CURRENT_LOG_LEVEL >= LOG_LEVEL_DEBUG) {249logDebug(tag, message);250showToast(context, message, true);251}252}253
254public static void logDebugAndShowToast(Context context, String message) {255logDebugAndShowToast(context, DEFAULT_LOG_TAG, message);256}257
258
259
260public static void logStackTraceWithMessage(String tag, String message, Throwable throwable) {261Logger.logErrorExtended(tag, getMessageAndStackTraceString(message, throwable));262}263
264public static void logStackTraceWithMessage(String message, Throwable throwable) {265logStackTraceWithMessage(DEFAULT_LOG_TAG, message, throwable);266}267
268public static void logStackTrace(String tag, Throwable throwable) {269logStackTraceWithMessage(tag, null, throwable);270}271
272public static void logStackTrace(Throwable throwable) {273logStackTraceWithMessage(DEFAULT_LOG_TAG, null, throwable);274}275
276
277
278public static void logStackTracesWithMessage(String tag, String message, List<Throwable> throwablesList) {279Logger.logErrorExtended(tag, getMessageAndStackTracesString(message, throwablesList));280}281
282
283
284public static String getMessageAndStackTraceString(String message, Throwable throwable) {285if (message == null && throwable == null)286return null;287else if (message != null && throwable != null)288return message + ":\n" + getStackTraceString(throwable);289else if (throwable == null)290return message;291else292return getStackTraceString(throwable);293}294
295public static String getMessageAndStackTracesString(String message, List<Throwable> throwablesList) {296if (message == null && (throwablesList == null || throwablesList.size() == 0))297return null;298else if (message != null && (throwablesList != null && throwablesList.size() != 0))299return message + ":\n" + getStackTracesString(null, getStackTracesStringArray(throwablesList));300else if (throwablesList == null || throwablesList.size() == 0)301return message;302else303return getStackTracesString(null, getStackTracesStringArray(throwablesList));304}305
306
307
308public static String getStackTraceString(Throwable throwable) {309if (throwable == null) return null;310
311String stackTraceString = null;312
313try {314StringWriter errors = new StringWriter();315PrintWriter pw = new PrintWriter(errors);316throwable.printStackTrace(pw);317pw.close();318stackTraceString = errors.toString();319errors.close();320} catch (IOException e) {321e.printStackTrace();322}323
324return stackTraceString;325}326
327
328
329public static String[] getStackTracesStringArray(Throwable throwable) {330return getStackTracesStringArray(Collections.singletonList(throwable));331}332
333public static String[] getStackTracesStringArray(List<Throwable> throwablesList) {334if (throwablesList == null) return null;335final String[] stackTraceStringArray = new String[throwablesList.size()];336for (int i = 0; i < throwablesList.size(); i++) {337stackTraceStringArray[i] = getStackTraceString(throwablesList.get(i));338}339return stackTraceStringArray;340}341
342
343
344public static String getStackTracesString(String label, String[] stackTraceStringArray) {345if (label == null) label = "StackTraces:";346StringBuilder stackTracesString = new StringBuilder(label);347
348if (stackTraceStringArray == null || stackTraceStringArray.length == 0) {349stackTracesString.append(" -");350} else {351for (int i = 0; i != stackTraceStringArray.length; i++) {352if (stackTraceStringArray.length > 1)353stackTracesString.append("\n\nStacktrace ").append(i + 1);354
355stackTracesString.append("\n```\n").append(stackTraceStringArray[i]).append("\n```\n");356}357}358
359return stackTracesString.toString();360}361
362public static String getStackTracesMarkdownString(String label, String[] stackTraceStringArray) {363if (label == null) label = "StackTraces";364StringBuilder stackTracesString = new StringBuilder("### " + label);365
366if (stackTraceStringArray == null || stackTraceStringArray.length == 0) {367stackTracesString.append("\n\n`-`");368} else {369for (int i = 0; i != stackTraceStringArray.length; i++) {370if (stackTraceStringArray.length > 1)371stackTracesString.append("\n\n\n#### Stacktrace ").append(i + 1);372
373stackTracesString.append("\n\n```\n").append(stackTraceStringArray[i]).append("\n```");374}375}376
377stackTracesString.append("\n##\n");378
379return stackTracesString.toString();380}381
382public static String getSingleLineLogStringEntry(String label, Object object, String def) {383if (object != null)384return label + ": `" + object + "`";385else386return label + ": " + def;387}388
389public static String getMultiLineLogStringEntry(String label, Object object, String def) {390if (object != null)391return label + ":\n```\n" + object + "\n```\n";392else393return label + ": " + def;394}395
396
397
398public static void showToast(final Context context, final String toastText, boolean longDuration) {399if (context == null || DataUtils.isNullOrEmpty(toastText)) return;400
401new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(context, toastText, longDuration ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show());402}403
404
405
406public static CharSequence[] getLogLevelsArray() {407return new CharSequence[]{408String.valueOf(LOG_LEVEL_OFF),409String.valueOf(LOG_LEVEL_NORMAL),410String.valueOf(LOG_LEVEL_DEBUG),411String.valueOf(LOG_LEVEL_VERBOSE)412};413}414
415public static CharSequence[] getLogLevelLabelsArray(Context context, CharSequence[] logLevels, boolean addDefaultTag) {416if (logLevels == null) return null;417
418CharSequence[] logLevelLabels = new CharSequence[logLevels.length];419
420for(int i=0; i<logLevels.length; i++) {421logLevelLabels[i] = getLogLevelLabel(context, Integer.parseInt(logLevels[i].toString()), addDefaultTag);422}423
424return logLevelLabels;425}426
427public static String getLogLevelLabel(final Context context, final int logLevel, final boolean addDefaultTag) {428String logLabel;429switch (logLevel) {430case LOG_LEVEL_OFF: logLabel = context.getString(R.string.log_level_off); break;431case LOG_LEVEL_NORMAL: logLabel = context.getString(R.string.log_level_normal); break;432case LOG_LEVEL_DEBUG: logLabel = context.getString(R.string.log_level_debug); break;433case LOG_LEVEL_VERBOSE: logLabel = context.getString(R.string.log_level_verbose); break;434default: logLabel = context.getString(R.string.log_level_unknown); break;435}436
437if (addDefaultTag && logLevel == DEFAULT_LOG_LEVEL)438return logLabel + " (default)";439else440return logLabel;441}442
443
444
445@NonNull446public static String getDefaultLogTag() {447return DEFAULT_LOG_TAG;448}449
450/**451* IllegalArgumentException will be thrown if tag.length() > 23 for Nougat (7.0) and prior releases.
452* https://developer.android.com/reference/android/util/Log#isLoggable(java.lang.String,%20int) */
453public static void setDefaultLogTag(@NonNull String defaultLogTag) {454DEFAULT_LOG_TAG = defaultLogTag.length() >= 23 ? defaultLogTag.substring(0, 22) : defaultLogTag;455}456
457
458
459public static int getLogLevel() {460return CURRENT_LOG_LEVEL;461}462
463public static int setLogLevel(Context context, int logLevel) {464if (isLogLevelValid(logLevel))465CURRENT_LOG_LEVEL = logLevel;466else467CURRENT_LOG_LEVEL = DEFAULT_LOG_LEVEL;468
469if (context != null)470showToast(context, context.getString(R.string.log_level_value, getLogLevelLabel(context, CURRENT_LOG_LEVEL, false)),true);471
472return CURRENT_LOG_LEVEL;473}474
475/** The colon character ":" must not exist inside the tag, otherwise the `logcat` command476* filterspecs arguments `<tag>[:priority]` will not work and will throw `Invalid filter expression`
477* error.
478* https://cs.android.com/android/platform/superproject/+/android-12.0.0_r4:system/logging/liblog/logprint.cpp;l=363
479* https://cs.android.com/android/platform/superproject/+/android-12.0.0_r4:system/logging/logcat/logcat.cpp;l=884
480* */
481public static String getFullTag(String tag) {482if (DEFAULT_LOG_TAG.equals(tag))483return tag;484else485return DEFAULT_LOG_TAG + "." + tag;486}487
488public static boolean isLogLevelValid(Integer logLevel) {489return (logLevel != null && logLevel >= LOG_LEVEL_OFF && logLevel <= MAX_LOG_LEVEL);490}491
492/** Check if custom log level is valid and >= {@link #CURRENT_LOG_LEVEL}. If custom log level is493* not valid then {@link #LOG_LEVEL_VERBOSE} must be >= {@link #CURRENT_LOG_LEVEL}. */
494public static boolean shouldEnableLoggingForCustomLogLevel(Integer customLogLevel) {495if (CURRENT_LOG_LEVEL <= LOG_LEVEL_OFF) return false;496if (customLogLevel == null) return CURRENT_LOG_LEVEL >= LOG_LEVEL_VERBOSE; // Use default app log level497if (customLogLevel <= LOG_LEVEL_OFF) return false;498customLogLevel = Logger.isLogLevelValid(customLogLevel) ? customLogLevel: Logger.LOG_LEVEL_VERBOSE;499return (customLogLevel >= CURRENT_LOG_LEVEL);500}501
502}
503