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:
- Handle uncaughtException in your Application subclass.
- After catching an exception, start a new activity to ask the user to send
a log. - Extract the log info from logcat's files and write to your
own file. - Start an email app, providing your file as an attachment.
- Manifest: filter your activity to be recognized by your exception handler.
- Optionally, setup Proguard to strip out
Log.d()
andLog.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
Context.Startforegroundservice() Did Not Then Call Service.Startforeground()
Offline Speech Recognition in Android (Jellybean)
How to Send String from One Activity to Another
API Key for Gcm Is Suddenly Invalid? Unauthorized (401) Error
Differencebetween Min Sdk Version/Target Sdk Version VS. Compile Sdk Version
Low Picture/Image Quality When Capture from Camera
Android Studio Marks R in Red with Error Message "Cannot Resolve Symbol R", But Build Succeeds
How to Simulate a Touch Event in Android
Android Pick Images from Gallery
How to Make a Phone Call in Android and Come Back to My Activity When the Call Is Done
How to Use Arrayadapter≪Myclass≫
How to Get Current Time from Internet in Android
Failed to Resolve: Com.Google.Firebase:Firebase-Core:11.2.0