Handle the uncaught exception and send the log file

UPDATE: see the โ€œacceptedโ€ decision below

When my application throws an unhandled exception, rather than just shutting down, I would like to first enable the user to send the log file. I understand that doing more work after receiving a random exception is risky, but, hey, the worst is that the application crashes and the log file is not sent. It turned out to be harder than I expected :)

What works: (1) catching an uncaught exception, (2) extracting log information and writing to a file.

What else does not work: (3) start of action for sending email. Ultimately, I will have another action to ask the user for permission. If I get a job by email, I do not expect big problems for another.

The crux of the problem is that the unhandled exception gets into my Application class. Since this is not an activity, it is not obvious how to get started with Intent.ACTION_SEND. That is, as a rule, startActivity is called to start the activity and resumes with onActivityResult. These methods are supported by Activity, but not Application.

Any suggestions on how to do this?

Here are some shortcuts to get you started:

public class MyApplication extends Application { defaultUncaughtHandler = Thread.getDefaultUncaughtExceptionHandler(); public void onCreate () { Thread.setDefaultUncaughtExceptionHandler (new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException (Thread thread, Throwable e) { handleUncaughtException (thread, e); } }); } private void handleUncaughtException (Thread thread, Throwable e) { String fullFileName = extractLogToFile(); // code not shown // The following shows what I'd like, though it won't work like this. Intent intent = new Intent (Intent.ACTION_SEND); intent.setType ("plain/text"); intent.putExtra (Intent.EXTRA_EMAIL, new String[] {"me@mydomain.com"}); intent.putExtra (Intent.EXTRA_SUBJECT, "log file"); intent.putExtra (Intent.EXTRA_STREAM, Uri.parse ("file://" + fullFileName)); startActivityForResult (intent, ACTIVITY_REQUEST_SEND_LOG); } public void onActivityResult (int requestCode, int resultCode, Intent data) { if (requestCode == ACTIVITY_REQUEST_SEND_LOG) System.exit(1); } } 
+101
android exception-handling logcat
Nov 11 '13 at 1:43
source share
6 answers

Here's the full solution (almost: I omitted the interface layout and button controls) - obtained from a large number of experiments and various messages from others related to problems that arose along the way.

You need to do a few things:

  • Handle uncaughtException in a subclass of Application.
  • Once an exception is detected, run a new action to ask the user to send a login.
  • Extract the log information from the logcat files and write to your own file.
  • Launch the email application by providing the file as an attachment.
  • Manifest: Filter your activity to recognize your exception handler.
  • If desired, install Proguard to cut Log.d () and Log.v ().

Now, here are the details:

(1 and 2) Handle uncaughtException, start working with the send log:

 public class MyApplication extends Application { public void onCreate () { // Setup handler for uncaught exceptions. Thread.setDefaultUncaughtExceptionHandler (new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException (Thread thread, Throwable e) { handleUncaughtException (thread, e); } }); } public void handleUncaughtException (Thread thread, Throwable e) { e.printStackTrace(); // not all Android versions will print the stack trace automatically Intent intent = new Intent (); intent.setAction ("com.mydomain.SEND_LOG"); // see step 5. intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK); // required when starting from Application startActivity (intent); System.exit(1); // kill off the crashed app } } 

(3) Extract the log (I set this with my SendLog processing):

 private String extractLogToFile() { PackageManager manager = this.getPackageManager(); PackageInfo info = null; try { info = manager.getPackageInfo (this.getPackageName(), 0); } catch (NameNotFoundException e2) { } String model = Build.MODEL; if (!model.startsWith(Build.MANUFACTURER)) model = Build.MANUFACTURER + " " + model; // Make file name - file must be saved to external storage or it wont be readable by // the email app. String path = Environment.getExternalStorageDirectory() + "/" + "MyApp/"; String fullName = path + <some name>; // Extract to file. File file = new File (fullName); InputStreamReader reader = null; FileWriter writer = null; try { // For Android 4.0 and earlier, you will get all app log output, so filter it to // mostly limit it to your app output. In later versions, the filtering isn't needed. String cmd = (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) ? "logcat -d -v time MyApp:v dalvikvm:v System.err:v *:s" : "logcat -d -v time"; // get input stream Process process = Runtime.getRuntime().exec(cmd); reader = new InputStreamReader (process.getInputStream()); // write output stream writer = new FileWriter (file); writer.write ("Android version: " + Build.VERSION.SDK_INT + "\n"); writer.write ("Device: " + model + "\n"); writer.write ("App version: " + (info == null ? "(null)" : info.versionCode) + "\n"); char[] buffer = new char[10000]; do { int n = reader.read (buffer, 0, buffer.length); if (n == -1) break; writer.write (buffer, 0, n); } while (true); reader.close(); writer.close(); } catch (IOException e) { if (writer != null) try { writer.close(); } catch (IOException e1) { } if (reader != null) try { reader.close(); } catch (IOException e1) { } // You might want to write a failure message to the log here. return null; } return fullName; } 

(4) Launch the email application (also in my SendLog operation):

 private void sendLogFile () { String fullName = extractLogToFile(); if (fullName == null) return; Intent intent = new Intent (Intent.ACTION_SEND); intent.setType ("plain/text"); intent.putExtra (Intent.EXTRA_EMAIL, new String[] {"log@mydomain.com"}); intent.putExtra (Intent.EXTRA_SUBJECT, "MyApp log file"); intent.putExtra (Intent.EXTRA_STREAM, Uri.parse ("file://" + fullName)); intent.putExtra (Intent.EXTRA_TEXT, "Log file attached."); // do this so some email clients don't complain about empty body. startActivity (intent); } 

(3 and 4) Here is what SendLog looks like (you need to add a user interface):

 public class SendLog extends Activity implements OnClickListener { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature (Window.FEATURE_NO_TITLE); // make a dialog without a titlebar setFinishOnTouchOutside (false); // prevent users from dismissing the dialog by tapping outside setContentView (R.layout.send_log); } @Override public void onClick (View v) { // respond to button clicks in your UI } private void sendLogFile () { // method as shown above } private String extractLogToFile() { // method as shown above } } 

(5) Manifesto:

 <manifest xmlns:android="http://schemas.android.com/apk/res/android" ... > <!-- needed for Android 4.0.x and eariler --> <uses-permission android:name="android.permission.READ_LOGS" /> <application ... > <activity android:name="com.mydomain.SendLog" android:theme="@android:style/Theme.Dialog" android:textAppearance="@android:style/TextAppearance.Large" android:windowSoftInputMode="stateHidden"> <intent-filter> <action android:name="com.mydomain.SEND_LOG" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> </application> </manifest> 

(6) Setup Proguard:

In project.properties, change the configuration line. You must specify "optimize", or Proguard will not delete Log.v () and Log.d () calls.

 proguard.config=${sdk.dir}/tools/proguard/proguard-android-optimize.txt:proguard-project.txt 

In proguard-project.txt add the following. This tells Proguard that Log.v and Log.d have no side effects (even though they are logged) and therefore can be removed during optimization:

 -assumenosideeffects class android.util.Log { public static int v(...); public static int d(...); } 

What is it! If you have any suggestions for improving this, let me know and I can update it.

+225
Nov 14 '13 at 2:28
source share

Today, there are many troubleshooting tools that make this easy.

  • crashlytics - crash reporting tool, free, but provides basic reports Benefits: Free

  • Gryphonet - A more advanced reporting tool requires some fees. Advantages: Easy rest accidents, ANR, slowness ...

If you are a private developer, I would suggest Crashlytics, but if it were a large organization, I would go to Gryphonet.

Good luck

+8
Oct. 15 '15 at 9:41
source share

Instead, try using ACRA โ€” it handles sending the stack trace, as well as many other useful debugging information, to your backend or Google Docs document that you configured.

https://github.com/ACRA/acra

+5
Jul 02 '15 at 18:13
source share

@PeriHartman's answer works well when a user interface thread throws an uncaught exception. I made some improvements when an uncaught exception is thrown by a non-interface thread.

 public boolean isUIThread(){ return Looper.getMainLooper().getThread() == Thread.currentThread(); } public void handleUncaughtException(Thread thread, Throwable e) { e.printStackTrace(); // not all Android versions will print the stack trace automatically if(isUIThread()) { invokeLogActivity(); }else{ //handle non UI thread throw uncaught exception new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { invokeLogActivity(); } }); } } private void invokeLogActivity(){ Intent intent = new Intent (); intent.setAction ("com.mydomain.SEND_LOG"); // see step 5. intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK); // required when starting from Application startActivity (intent); System.exit(1); // kill off the crashed app } 
+4
Aug 18 '15 at 2:25
source share

Well explained. But one note here, instead of writing to a file using File Writer and Streaming, I used the logcat -f option directly. Here is the code

 String[] cmd = new String[] {"logcat","-f",filePath,"-v","time","<MyTagName>:D","*:S"}; try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } 

This helped me clear the latest buffer information. Using streaming files gave me one problem: it did not flush the latest logs from the buffer. But in any case, this was a really useful guide. Thank.

+2
Apr 10 '14 at 0:24
source share

You can handle unhandled exceptions using the FireCrasher library and recover from it.

You can learn more about the library in this middle article.

0
Mar 27 '19 at 15:53
source share



All Articles