Skip to content

Commit 1ec4a88

Browse files
feat(YouTube - Enable debugging): Add settings menu to share debug logs (#5021)
Co-authored-by: LisoUseInAIKyrios <[email protected]>
1 parent bbe7974 commit 1ec4a88

File tree

12 files changed

+391
-113
lines changed

12 files changed

+391
-113
lines changed
Lines changed: 137 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,185 @@
11
package app.revanced.extension.shared;
22

3+
import static app.revanced.extension.shared.settings.BaseSettings.DEBUG;
4+
import static app.revanced.extension.shared.settings.BaseSettings.DEBUG_STACKTRACE;
5+
import static app.revanced.extension.shared.settings.BaseSettings.DEBUG_TOAST_ON_ERROR;
6+
37
import android.util.Log;
8+
49
import androidx.annotation.NonNull;
510
import androidx.annotation.Nullable;
6-
import app.revanced.extension.shared.settings.BaseSettings;
711

812
import java.io.PrintWriter;
913
import java.io.StringWriter;
1014

11-
import static app.revanced.extension.shared.settings.BaseSettings.*;
12-
15+
import app.revanced.extension.shared.settings.BaseSettings;
16+
import app.revanced.extension.shared.settings.preference.LogBufferManager;
17+
18+
/**
19+
* ReVanced specific logger. Logging is done to standard device log (accessible thru ADB),
20+
* and additionally accessible thru {@link LogBufferManager}.
21+
*
22+
* All methods are thread safe.
23+
*/
1324
public class Logger {
1425

1526
/**
1627
* Log messages using lambdas.
1728
*/
1829
@FunctionalInterface
1930
public interface LogMessage {
31+
/**
32+
* @return Logger string message. This method is only called if logging is enabled.
33+
*/
2034
@NonNull
2135
String buildMessageString();
36+
}
2237

23-
/**
24-
* @return For outer classes, this returns {@link Class#getSimpleName()}.
25-
* For static, inner, or anonymous classes, this returns the simple name of the enclosing class.
26-
* <br>
27-
* For example, each of these classes return 'SomethingView':
28-
* <code>
29-
* com.company.SomethingView
30-
* com.company.SomethingView$StaticClass
31-
* com.company.SomethingView$1
32-
* </code>
33-
*/
34-
private String findOuterClassSimpleName() {
35-
var selfClass = this.getClass();
38+
private enum LogLevel {
39+
DEBUG,
40+
INFO,
41+
ERROR
42+
}
3643

37-
String fullClassName = selfClass.getName();
38-
final int dollarSignIndex = fullClassName.indexOf('$');
39-
if (dollarSignIndex < 0) {
40-
return selfClass.getSimpleName(); // Already an outer class.
41-
}
44+
private static final String REVANCED_LOG_TAG = "revanced";
45+
46+
private static final String LOGGER_CLASS_NAME = Logger.class.getName();
4247

43-
// Class is inner, static, or anonymous.
44-
// Parse the simple name full name.
45-
// A class with no package returns index of -1, but incrementing gives index zero which is correct.
46-
final int simpleClassNameStartIndex = fullClassName.lastIndexOf('.') + 1;
47-
return fullClassName.substring(simpleClassNameStartIndex, dollarSignIndex);
48+
/**
49+
* @return For outer classes, this returns {@link Class#getSimpleName()}.
50+
* For static, inner, or anonymous classes, this returns the simple name of the enclosing class.
51+
* <br>
52+
* For example, each of these classes returns 'SomethingView':
53+
* <code>
54+
* com.company.SomethingView
55+
* com.company.SomethingView$StaticClass
56+
* com.company.SomethingView$1
57+
* </code>
58+
*/
59+
private static String getOuterClassSimpleName(Object obj) {
60+
Class<?> logClass = obj.getClass();
61+
String fullClassName = logClass.getName();
62+
final int dollarSignIndex = fullClassName.indexOf('$');
63+
if (dollarSignIndex < 0) {
64+
return logClass.getSimpleName(); // Already an outer class.
4865
}
66+
67+
// Class is inner, static, or anonymous.
68+
// Parse the simple name full name.
69+
// A class with no package returns index of -1, but incrementing gives index zero which is correct.
70+
final int simpleClassNameStartIndex = fullClassName.lastIndexOf('.') + 1;
71+
return fullClassName.substring(simpleClassNameStartIndex, dollarSignIndex);
4972
}
5073

51-
private static final String REVANCED_LOG_PREFIX = "revanced: ";
74+
/**
75+
* Internal method to handle logging to Android Log and {@link LogBufferManager}.
76+
* Appends the log message, stack trace (if enabled), and exception (if present) to logBuffer
77+
* with class name but without 'revanced:' prefix.
78+
*
79+
* @param logLevel The log level.
80+
* @param message Log message object.
81+
* @param ex Optional exception.
82+
* @param includeStackTrace If the current stack should be included.
83+
* @param showToast If a toast is to be shown.
84+
*/
85+
private static void logInternal(LogLevel logLevel, LogMessage message, @Nullable Throwable ex,
86+
boolean includeStackTrace, boolean showToast) {
87+
// It's very important that no Settings are used in this method,
88+
// as this code is used when a context is not set and thus referencing
89+
// a setting will crash the app.
90+
String messageString = message.buildMessageString();
91+
String className = getOuterClassSimpleName(message);
92+
93+
StringBuilder logBuilder = new StringBuilder(className.length() + 2
94+
+ messageString.length());
95+
logBuilder.append(className).append(": ").append(messageString);
96+
97+
String toastMessage = showToast ? logBuilder.toString() : null;
98+
99+
// Append exception message if present.
100+
if (ex != null) {
101+
var exceptionMessage = ex.getMessage();
102+
if (exceptionMessage != null) {
103+
logBuilder.append("\nException: ").append(exceptionMessage);
104+
}
105+
}
106+
107+
if (includeStackTrace) {
108+
var sw = new StringWriter();
109+
new Throwable().printStackTrace(new PrintWriter(sw));
110+
String stackTrace = sw.toString();
111+
// Remove the stacktrace elements of this class.
112+
final int loggerIndex = stackTrace.lastIndexOf(LOGGER_CLASS_NAME);
113+
final int loggerBegins = stackTrace.indexOf('\n', loggerIndex);
114+
logBuilder.append(stackTrace, loggerBegins, stackTrace.length());
115+
}
116+
117+
String logText = logBuilder.toString();
118+
LogBufferManager.appendToLogBuffer(logText);
119+
120+
switch (logLevel) {
121+
case DEBUG:
122+
if (ex == null) Log.d(REVANCED_LOG_TAG, logText);
123+
else Log.d(REVANCED_LOG_TAG, logText, ex);
124+
break;
125+
case INFO:
126+
if (ex == null) Log.i(REVANCED_LOG_TAG, logText);
127+
else Log.i(REVANCED_LOG_TAG, logText, ex);
128+
break;
129+
case ERROR:
130+
if (ex == null) Log.e(REVANCED_LOG_TAG, logText);
131+
else Log.e(REVANCED_LOG_TAG, logText, ex);
132+
break;
133+
}
134+
135+
if (toastMessage != null) {
136+
Utils.showToastLong(toastMessage);
137+
}
138+
}
52139

53140
/**
54141
* Logs debug messages under the outer class name of the code calling this method.
55-
* Whenever possible, the log string should be constructed entirely inside {@link LogMessage#buildMessageString()}
56-
* so the performance cost of building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
142+
* <p>
143+
* Whenever possible, the log string should be constructed entirely inside
144+
* {@link LogMessage#buildMessageString()} so the performance cost of
145+
* building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
57146
*/
58-
public static void printDebug(@NonNull LogMessage message) {
147+
public static void printDebug(LogMessage message) {
59148
printDebug(message, null);
60149
}
61150

62151
/**
63152
* Logs debug messages under the outer class name of the code calling this method.
64-
* Whenever possible, the log string should be constructed entirely inside {@link LogMessage#buildMessageString()}
65-
* so the performance cost of building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
153+
* <p>
154+
* Whenever possible, the log string should be constructed entirely inside
155+
* {@link LogMessage#buildMessageString()} so the performance cost of
156+
* building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
66157
*/
67-
public static void printDebug(@NonNull LogMessage message, @Nullable Exception ex) {
158+
public static void printDebug(LogMessage message, @Nullable Exception ex) {
68159
if (DEBUG.get()) {
69-
String logMessage = message.buildMessageString();
70-
String logTag = REVANCED_LOG_PREFIX + message.findOuterClassSimpleName();
71-
72-
if (DEBUG_STACKTRACE.get()) {
73-
var builder = new StringBuilder(logMessage);
74-
var sw = new StringWriter();
75-
new Throwable().printStackTrace(new PrintWriter(sw));
76-
77-
builder.append('\n').append(sw);
78-
logMessage = builder.toString();
79-
}
80-
81-
if (ex == null) {
82-
Log.d(logTag, logMessage);
83-
} else {
84-
Log.d(logTag, logMessage, ex);
85-
}
160+
logInternal(LogLevel.DEBUG, message, ex, DEBUG_STACKTRACE.get(), false);
86161
}
87162
}
88163

89164
/**
90165
* Logs information messages using the outer class name of the code calling this method.
91166
*/
92-
public static void printInfo(@NonNull LogMessage message) {
167+
public static void printInfo(LogMessage message) {
93168
printInfo(message, null);
94169
}
95170

96171
/**
97172
* Logs information messages using the outer class name of the code calling this method.
98173
*/
99-
public static void printInfo(@NonNull LogMessage message, @Nullable Exception ex) {
100-
String logTag = REVANCED_LOG_PREFIX + message.findOuterClassSimpleName();
101-
String logMessage = message.buildMessageString();
102-
if (ex == null) {
103-
Log.i(logTag, logMessage);
104-
} else {
105-
Log.i(logTag, logMessage, ex);
106-
}
174+
public static void printInfo(LogMessage message, @Nullable Exception ex) {
175+
logInternal(LogLevel.INFO, message, ex, DEBUG_STACKTRACE.get(), false);
107176
}
108177

109178
/**
110179
* Logs exceptions under the outer class name of the code calling this method.
180+
* Appends the log message, exception (if present), and toast message (if enabled) to logBuffer.
111181
*/
112-
public static void printException(@NonNull LogMessage message) {
182+
public static void printException(LogMessage message) {
113183
printException(message, null);
114184
}
115185

@@ -122,35 +192,23 @@ public static void printException(@NonNull LogMessage message) {
122192
* @param message log message
123193
* @param ex exception (optional)
124194
*/
125-
public static void printException(@NonNull LogMessage message, @Nullable Throwable ex) {
126-
String messageString = message.buildMessageString();
127-
String outerClassSimpleName = message.findOuterClassSimpleName();
128-
String logMessage = REVANCED_LOG_PREFIX + outerClassSimpleName;
129-
if (ex == null) {
130-
Log.e(logMessage, messageString);
131-
} else {
132-
Log.e(logMessage, messageString, ex);
133-
}
134-
if (DEBUG_TOAST_ON_ERROR.get()) {
135-
Utils.showToastLong(outerClassSimpleName + ": " + messageString);
136-
}
195+
public static void printException(LogMessage message, @Nullable Throwable ex) {
196+
logInternal(LogLevel.ERROR, message, ex, DEBUG_STACKTRACE.get(), DEBUG_TOAST_ON_ERROR.get());
137197
}
138198

139199
/**
140200
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
141201
* Normally this method should not be used.
142202
*/
143-
public static void initializationInfo(@NonNull Class<?> callingClass, @NonNull String message) {
144-
Log.i(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message);
203+
public static void initializationInfo(LogMessage message) {
204+
logInternal(LogLevel.INFO, message, null, false, false);
145205
}
146206

147207
/**
148208
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
149209
* Normally this method should not be used.
150210
*/
151-
public static void initializationException(@NonNull Class<?> callingClass, @NonNull String message,
152-
@Nullable Exception ex) {
153-
Log.e(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message, ex);
211+
public static void initializationException(LogMessage message, @Nullable Exception ex) {
212+
logInternal(LogLevel.ERROR, message, ex, false, false);
154213
}
155-
156-
}
214+
}

0 commit comments

Comments
 (0)