Need to handle uncaught exception and send log file

后端 未结 7 789
时光说笑
时光说笑 2020-11-22 15:42

UPDATE: Please see \"accepted\" solution below

When my app creates an unhandled exception, rather than simply terminating, I\'d like to first give t

相关标签:
7条回答
  • 2020-11-22 16:09

    Try using ACRA instead - it handles sending the stack trace as well as tons of other useful debug information to your backend, or to Google Docs document you've set up.

    https://github.com/ACRA/acra

    0 讨论(0)
  • 2020-11-22 16:10

    @PeriHartman's answer works well when the UI thread throws uncaught exception. I made some improvements for when the uncaught exception is thrown by a non UI 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
    }
    
    0 讨论(0)
  • 2020-11-22 16:16

    Today there are many crash reprting tools that do this easily.

    1. crashlytics - A crash reporting tool, free of charge but gives you basic reports Advantages : Free

    2. Gryphonet - A more advanced reporting tool, requires some kind of fee. Advantages : Easy recreation of crashes, ANR's, slowness...

    If you are a private developer I would suggest Crashlytics, but if it's a big organization, I would go for Gryphonet.

    Good Luck!

    0 讨论(0)
  • 2020-11-22 16:19

    Here's the complete solution (almost: I omitted the UI layout and button handling) - derived from a lot of experimentation and various posts from others related to issues that came up along the way.

    There are a number of things you need to do:

    1. Handle uncaughtException in your Application subclass.
    2. After catching an exception, start a new activity to ask the user to send a log.
    3. Extract the log info from logcat's files and write to your own file.
    4. Start an email app, providing your file as an attachment.
    5. Manifest: filter your activity to be recognized by your exception handler.
    6. Optionally, setup Proguard to strip out Log.d() and Log.v().

    Now, here are the details:

    (1 & 2) Handle uncaughtException, start send log activity:

    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 log (I put this an my SendLog Activity):

    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's log output, so filter it to
        // mostly limit it to your app's 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) Start an email app (also in my SendLog Activity):

    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 & 4) Here's what SendLog looks like (you'll have to add the UI, though):

    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) Manifest:

    <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 config line. You must specify "optimize" or Proguard will not remove 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 tell Proguard to assume Log.v and Log.d have no side effects (even though they do since they write to the logs) and thus can be removed during optimization:

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

    That's it! If you have any suggestions for improvements to this, please let me know and I may update this.

    0 讨论(0)
  • 2020-11-22 16:25

    Handling uncaught Exceptions: as @gilm explained just do this, (kotlin):

    private val defaultUncaughtHandler = Thread.getDefaultUncaughtExceptionHandler();
    
    override fun onCreate() {
      //...
        Thread.setDefaultUncaughtExceptionHandler { t, e ->
            Crashlytics.logException(e)
            defaultUncaughtHandler?.uncaughtException(t, e)
        }
    }
    

    I hope it helps, it worked for me.. (:y). In my case I've used 'com.microsoft.appcenter.crashes.Crashes' library for error tracking.

    0 讨论(0)
  • 2020-11-22 16:35

    Nicely explained. But one observation here, instead of writing into file using File Writer and Streaming, I made use of 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 in flushing the latest buffer info. Using File streaming gave me one issue that it was not flushing the latest logs from buffer. But anyway, this was really helpful guide. Thank you.

    0 讨论(0)
提交回复
热议问题