termux-app
158 строк · 7.2 Кб
1package com.termux.shared.crash;
2
3import android.content.Context;
4
5import androidx.annotation.NonNull;
6
7import com.termux.shared.file.FileUtils;
8import com.termux.shared.logger.Logger;
9import com.termux.shared.markdown.MarkdownUtils;
10import com.termux.shared.errors.Error;
11import com.termux.shared.android.AndroidUtils;
12
13import java.nio.charset.Charset;
14
15/**
16* Catches uncaught exceptions and logs them.
17*/
18public class CrashHandler implements Thread.UncaughtExceptionHandler {
19
20private final Context mContext;
21private final CrashHandlerClient mCrashHandlerClient;
22private final Thread.UncaughtExceptionHandler mDefaultUEH;
23private final boolean mIsDefaultHandler;
24
25private static final String LOG_TAG = "CrashUtils";
26
27private CrashHandler(@NonNull final Context context, @NonNull final CrashHandlerClient crashHandlerClient,
28boolean isDefaultHandler) {
29mContext = context;
30mCrashHandlerClient = crashHandlerClient;
31mDefaultUEH = Thread.getDefaultUncaughtExceptionHandler();
32mIsDefaultHandler = isDefaultHandler;
33}
34
35public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) {
36Logger.logInfo(LOG_TAG, "uncaughtException() for " + thread + ": " + throwable.getMessage());
37logCrash(thread, throwable);
38
39// Don't stop the app if not on the main thread
40if (mIsDefaultHandler)
41mDefaultUEH.uncaughtException(thread, throwable);
42}
43
44/**
45* Set default uncaught crash handler for the app to {@link CrashHandler}.
46*/
47public static void setDefaultCrashHandler(@NonNull final Context context, @NonNull final CrashHandlerClient crashHandlerClient) {
48if (!(Thread.getDefaultUncaughtExceptionHandler() instanceof CrashHandler)) {
49Thread.setDefaultUncaughtExceptionHandler(new CrashHandler(context, crashHandlerClient, true));
50}
51}
52
53/**
54* Set uncaught crash handler of current non-main thread to {@link CrashHandler}.
55*/
56public static void setCrashHandler(@NonNull final Context context, @NonNull final CrashHandlerClient crashHandlerClient) {
57Thread.currentThread().setUncaughtExceptionHandler(new CrashHandler(context, crashHandlerClient, false));
58}
59
60/**
61* Get {@link CrashHandler} instance that can be set as uncaught crash handler of a non-main thread.
62*/
63public static CrashHandler getCrashHandler(@NonNull final Context context, @NonNull final CrashHandlerClient crashHandlerClient) {
64return new CrashHandler(context, crashHandlerClient, false);
65}
66
67/**
68* Log a crash in the crash log file at path returned by {@link CrashHandlerClient#getCrashLogFilePath(Context)}.
69*
70* @param context The {@link Context} for operations.
71* @param crashHandlerClient The {@link CrashHandlerClient} implementation.
72* @param thread The {@link Thread} in which the crash happened.
73* @param throwable The {@link Throwable} thrown for the crash.
74*/
75public static void logCrash(@NonNull Context context,
76@NonNull CrashHandlerClient crashHandlerClient,
77@NonNull Thread thread, @NonNull Throwable throwable) {
78Logger.logInfo(LOG_TAG, "logCrash() for " + thread + ": " + throwable.getMessage());
79new CrashHandler(context, crashHandlerClient, false).logCrash(thread, throwable);
80}
81
82public void logCrash(@NonNull Thread thread, @NonNull Throwable throwable) {
83if (!mCrashHandlerClient.onPreLogCrash(mContext, thread, throwable)) {
84logCrashToFile(mContext, mCrashHandlerClient, thread, throwable);
85mCrashHandlerClient.onPostLogCrash(mContext, thread, throwable);
86}
87}
88
89public void logCrashToFile(@NonNull Context context,
90@NonNull CrashHandlerClient crashHandlerClient,
91@NonNull Thread thread, @NonNull Throwable throwable) {
92StringBuilder reportString = new StringBuilder();
93
94reportString.append("## Crash Details\n");
95reportString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Crash Thread", thread.toString(), "-"));
96reportString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Crash Timestamp", AndroidUtils.getCurrentMilliSecondUTCTimeStamp(), "-"));
97reportString.append("\n\n").append(MarkdownUtils.getMultiLineMarkdownStringEntry("Crash Message", throwable.getMessage(), "-"));
98reportString.append("\n\n").append(Logger.getStackTracesMarkdownString("Stacktrace", Logger.getStackTracesStringArray(throwable)));
99
100String appInfoMarkdownString = crashHandlerClient.getAppInfoMarkdownString(context);
101if (appInfoMarkdownString != null && !appInfoMarkdownString.isEmpty())
102reportString.append("\n\n").append(appInfoMarkdownString);
103
104reportString.append("\n\n").append(AndroidUtils.getDeviceInfoMarkdownString(context));
105
106// Log report string to logcat
107Logger.logError(reportString.toString());
108
109// Write report string to crash log file
110Error error = FileUtils.writeTextToFile("crash log", crashHandlerClient.getCrashLogFilePath(context),
111Charset.defaultCharset(), reportString.toString(), false);
112if (error != null) {
113Logger.logErrorExtended(LOG_TAG, error.toString());
114}
115}
116
117public interface CrashHandlerClient {
118
119/**
120* Called before {@link #logCrashToFile(Context, CrashHandlerClient, Thread, Throwable)} is called.
121*
122* @param context The {@link Context} passed to {@link CrashHandler#CrashHandler(Context, CrashHandlerClient, boolean)}.
123* @param thread The {@link Thread} in which the crash happened.
124* @param throwable The {@link Throwable} thrown for the crash.
125* @return Should return {@code true} if crash has been handled and should not be logged,
126* otherwise {@code false}.
127*/
128boolean onPreLogCrash(Context context, Thread thread, Throwable throwable);
129
130/**
131* Called after {@link #logCrashToFile(Context, CrashHandlerClient, Thread, Throwable)} is called.
132*
133* @param context The {@link Context} passed to {@link CrashHandler#CrashHandler(Context, CrashHandlerClient, boolean)}.
134* @param thread The {@link Thread} in which the crash happened.
135* @param throwable The {@link Throwable} thrown for the crash.
136*/
137void onPostLogCrash(Context context, Thread thread, Throwable throwable);
138
139/**
140* Get crash log file path.
141*
142* @param context The {@link Context} passed to {@link CrashHandler#CrashHandler(Context, CrashHandlerClient, boolean)}.
143* @return Should return the crash log file path.
144*/
145@NonNull
146String getCrashLogFilePath(Context context);
147
148/**
149* Get app info markdown string to add to crash log.
150*
151* @param context The {@link Context} passed to {@link CrashHandler#CrashHandler(Context, CrashHandlerClient, boolean)}.
152* @return Should return app info markdown string.
153*/
154String getAppInfoMarkdownString(Context context);
155
156}
157
158}
159