Need to Handle Uncaught Exception and Send Log File

Need to handle uncaught exception and send log file

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.example"});
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.

How do I handle uncaught exceptions and then delegate handling back to the system?

The uncaught exception can be delegated back to system by storing the old exception handler and passing uncaught exceptions to it.

First create an Application class as below:

public class Controller extends Application {

private static Thread.UncaughtExceptionHandler defaultHandler;

@Override
public void onCreate() {
super.onCreate();
if (defaultHandler == null) {
defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
FirebaseCrash.report(e); //or whatever
defaultHandler.uncaughtException(t, e); //this will show crash dialog.
}
});
}

}

Then use this class as application in the manifest as:

<application
android:name=".Controller"
... />

How should I handle exceptions thrown by logger while handling another exception?

Logging of exceptions and notification about them are two tasks which must be solved globally for the whole project. They shouldn't be solved with help try-catch blocks, because as a usual thing try-catch should be used to try to resolve the concrete local located problems which made an exception (for example, modify data or try to repeat execution) or to do actions to restore an application state. Logging and notification about exceptions are tasks which should be solved with a global exception handler. PHP has a native mechanism to configure an exception handler with set_exception_handler function. For example:

function handle_exception(Exception $exception)
{
//do something, for example, store an exception to log file
}

set_exception_handler('handle_exception');

After configuring handler, all thrown exception will be handled with handle_exception() function. For example:

function handle_exception(Exception $exception)
{
echo $exception->getMessage();
}

set_exception_handler('handle_exception');
// some code
throw Exception('Some error was happened');

Also, you can always disable the current exception handler with help restore_exception_handler function.

In your case, you can create a simple exception handler class which will contain logging methods and notification methods and implement a mechanism to handle exceptions which will select a necessary method. For example:

class ExceptionHandler
{
/**
* Store an exception into a log file
* @param Exception $exception the exception that'll be sent
*/
protected function storeToLog(Exception $exception)
{}

/**
* Send an exception to the email address
* @param Exception $exception the exception that'll be sent
*/
protected function sendToEmail(Exception $exception)
{}

/**
* Do some other actions with an exception
* @param Exception $exception the exception that'll be handled
*/
protected function doSomething(Exception $exception)
{}

/**
* Handle an exception
* @param Exception $exception the exception that'll be handled
*/
public function handle(Exception $exception)
{
try {
// try to store the exception to log file
$this->storeToLog($exception);
} catch (Exception $exception) {
try {
// if the exception wasn't stored to log file
// then try to send the exception via email
$this->sendToEmail($exception);
} catch (Exception $exception) {
// if the exception wasn't stored to log file
// and wasn't send via email
// then try to do something else
$this->doSomething($exception);
}
}

}
}

After it, you can register this handler

$handler = new ExceptionHandler();
set_exception_handler([$handler, 'handle']);


$routes = require_once(__DIR__.'/Routes.php');

$router = new RouteFactory($routes, $request, \Skletter\View\ErrorPages::class);
$router->buildPaths('Skletter\Controller\\', 'Skletter\View\\');

$app = new Application($injector);
$app->run($request, $router);

Logging uncaught exceptions in Python

As Ned pointed out, sys.excepthook is invoked every time an exception is raised and uncaught. The practical implication of this is that in your code you can override the default behavior of sys.excepthook to do whatever you want (including using logging.exception).

As a straw man example:

import sys
def foo(exctype, value, tb):
print('My Error Information')
print('Type:', exctype)
print('Value:', value)
print('Traceback:', tb)

Override sys.excepthook:

>>> sys.excepthook = foo

Commit obvious syntax error (leave out the colon) and get back custom error information:

>>> def bar(a, b)
My Error Information
Type: <type 'exceptions.SyntaxError'>
Value: invalid syntax (<stdin>, line 1)
Traceback: None

For more information about sys.excepthook, read the docs.

Logging unhandled exceptions in Android Activity

I managed to finally solve this with help of AAG's answer.

@AAG you were right, the exception handler was being added each time an onCreate() method in any activity was called.

@AAG But you were wrong about not using defaultHandler. I have to use it, for Android to properly handle application crash.

Thanks for your help!

This is the fixed code:

public class BaseActivity extends AppCompatActivity {

public static Context applicationContext = null;
public static Thread.UncaughtExceptionHandler defaultHandler = null;
public static Thread.UncaughtExceptionHandler exceptionHandler = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if(defaultHandler == null){
defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}

if(applicationContext == null){
applicationContext = getApplicationContext();
}

if(exceptionHandler == null){
exceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread paramThread, Throwable paramThrowable) {
Log.e("Uncaught Exception", paramThrowable.getMessage());
logError(paramThrowable);
defaultHandler.uncaughtException(paramThread, paramThrowable);

}
};

Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
}
}

private static void logError(final Throwable paramThrowable){
try {
ApplicationError error = new ApplicationError();

String stackTrace = "";
for (int i = 0; i < paramThrowable.getStackTrace().length; i++) {
stackTrace += paramThrowable.getStackTrace()[i].toString() + "\n";
}

Log.e("Saving error...", "");

Throwable tmp = paramThrowable;
int j = 0;
while ((tmp = tmp.getCause()) != null && j < 5) {
j++;
stackTrace += "Coused by:\n";
for (int i = 0; i < tmp.getStackTrace().length; i++) {
stackTrace += tmp.getStackTrace()[i].toString() + "\n";
}
}

String deviceInfo = "";
deviceInfo += "OS version: " + System.getProperty("os.version") + "\n";
deviceInfo += "API level: " + Build.VERSION.SDK_INT + "\n";
deviceInfo += "Manufacturer: " + Build.MANUFACTURER + "\n";
deviceInfo += "Device: " + Build.DEVICE + "\n";
deviceInfo += "Model: " + Build.MODEL + "\n";
deviceInfo += "Product: " + Build.PRODUCT + "\n";

error.mDeviceInfo = deviceInfo;
error.mErrorMessage = paramThrowable.getMessage();
error.mStackTrace = stackTrace;

error.save();

Log.e("Saved error:", error.mErrorMessage + "\n" + error.mStackTrace);
}catch(Exception e){

}
}

}


Related Topics



Leave a reply



Submit