Sending crash reports with ACRA over email (using Mandrill)

Sending crash reports with ACRA over email (using Mandrill)

 What is ACRA?

ACRA stands for Application Crash Reports for Android. It is library enabling Android applications to send crash reports to developer in order to debug and get insights if/when application crashes or behaves erroneously. As stated on their github page, ACRA is running on over a billion devices.

What makes ACRA an essential part of every Android application is that it can send crash report without user interaction, even when application is not yet launched on Google Play. It comes handy when application is under internal testing or UAT or if its an enterprise private app.

ACRA provides silent reports, toasts, status bar notification with dialog and direct dialog. We are concerned with silent reports where user won’t be notified when an app crashes and a crash report is sent to developer in background.

In this post we are looking at how to send this crash report over email, we are going to accomplish it using MandrillApp. MandrillApp is an email infrastructure service. It doesn’t get simpler than this. Keep reading.

CREATE AN ACCOUNT ON MANDRILLAPP

 Sign up for it here . Same account can then be used for multiple applications. After logging in, you will see a dashboard like below.

 

Mandrill Dashboard

Go to “Settings” in left pane, select “SMTP and API info”.

 Mandrill settings screenshot

After selecting SMTP and API info, click on “New API key”.

Create a new API key for your application, enter a description for you app. You have to create multiple keys for different applications.

 Note: If you added ACRA in an application long time ago and now that applications is no longer supported or you don’t wish to receive crash reports any longer then you can edit/delete the key from this section. 

You can see your recently key created. Copy and save this key, we will use this key in our application to send crash reports over email.

 CREATE A NEW SAMPLE PROJECT

 Let’s create a dummy project to illustrate what we are doing. I have only added a line to make the app crash just upon launching.

public class MainActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        int x = 1 / 0;

    }


}

CONFIGURE PROJECT TO USE ACRA

Now we need to include ACRA in our project. If you are using AndroidStudio/Gradle then you can include following in build.gradle and be done with it.

compile 'ch.acra:acra:4.5.0'

Alternatively, you can add acra-4.X.Y.jar jar as well. 

Now, you need to add an Application class to your project. If you already have an Application class then don’t add, just add ACRA to it.

Create a class and name it like “MyApp” or something. This class should extend Application.

 We will add @ReportsCrashes annotation just above the class name with our mandrill app request URL as formUri (as advised on https://github.com/ACRA/acra/wiki/BasicSetup)

 We also need to override onCreate() method of our newly created class and add following: 


ACRA.init(this);

This is how our class looks like now:

@ReportsCrashes(formKey = "", // will not be used
        formUri = "http://mandrillapp.com/api/1.0/messages/send.json"
)

public class MyApp extends Application {
    private ReportsCrashes mReportsCrashes;

    @Override
    public void onCreate() {
        super.onCreate();

        ACRA.init(this);

    }
}

SETUP ACRA TO SEND EMAIL

So far, ACRA is all set to capture error reports but it will just sit on it. To make it send email to your email address, we need to add JsonSender class. 

Create a class with name JsonSender. Paste following in that class.



public class JsonSender implements ReportSender {
 private Uri mFormUri = null;
 private Map<ReportField, String> mMapping = null;
 private static final String CONTENT_TYPE;

static {
 CONTENT_TYPE = "application/json";
 }

/**
 * <p>
 * Create a new HttpPostSender instance.
 * </p>
 *
 * @param formUri The URL of your server-side crash report collection script.
 * @param mapping If null, POST parameters will be named with
 * {@link org.acra.ReportField} values converted to String with
 * .toString(). If not null, POST parameters will be named with
 * the result of mapping.get(ReportField.SOME_FIELD);
 */
 public JsonSender(String formUri, Map<ReportField, String> mapping) {
 mFormUri = Uri.parse(formUri);
 mMapping = mapping;
 }

public void send(CrashReportData report) throws ReportSenderException {

try {
 URL reportUrl;
 reportUrl = new URL(mFormUri.toString());
 Log.d(LOG_TAG, "Connect to " + reportUrl.toString());

JSONObject json = createJSON(report);

sendHttpPost(json.toString(), reportUrl, ACRA.getConfig().formUriBasicAuthLogin(), ACRA.getConfig().formUriBasicAuthPassword());

} catch (Exception e) {
 throw new ReportSenderException("Error while sending report to Http Post Form.", e);
 }

}

private static boolean isNull(String aString) {
 return aString == null || aString == null;

}

private JSONObject createJSON(Map<ReportField, String> report) {
 JSONObject json = new JSONObject();

ReportField[] fields = ACRA.getConfig().customReportContent();
 if (fields.length == 0) {
 fields = ACRAConstants.DEFAULT_REPORT_FIELDS;
 }
 for (ReportField field : fields) {
 try {
 if (mMapping == null || mMapping.get(field) == null) {
 json.put(field.toString(), report.get(field));
 } else {
 json.put(mMapping.get(field), report.get(field));
 }
 } catch (JSONException e) {
 Log.e("JSONException", "There was an error creating JSON", e);
 }
 }

return json;
 }

//TODO: login + password
 //(isNull(login) ? null : login, isNull(password) ? null : password);
 private void sendHttpPost(String data, URL url, String login, String password) {
 DefaultHttpClient httpClient = new DefaultHttpClient();
 try {
 HttpPost httPost = new HttpPost(url.toString());

List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();

JSONObject jsonObject = new JSONObject();

data = data.replace("\\n", " \n ");
 data = data.replace("\\", "");
 StringEntity se = new StringEntity(getDataForMandrill(data));
 httPost.setEntity(se);

//sets a request header so the page receving the request
 //will know what to do with it
 httPost.setHeader("Accept", "application/json");
 httPost.setHeader("Content-type", "application/json");


 HttpResponse httpResponse = httpClient.execute(httPost);

Log.d(LOG_TAG, "Server Status: " + httpResponse.getStatusLine());
 Log.d(LOG_TAG, "Server Response: " + EntityUtils.toString(httpResponse.getEntity()));


 } catch (ClientProtocolException e) {
 e.printStackTrace();
 } catch (UnsupportedEncodingException e) {
 e.printStackTrace();
 } catch (IOException e) {
 e.printStackTrace();
 } finally {
 httpClient.getConnectionManager().shutdown();
 }
 }

private String getDataForMandrill(String data) {


 try {

JSONObject email_json = new JSONObject();
 email_json.put("email", "ay********al@gmail.com"); // recipient email address goes here
 email_json.put("name", "Ayush"); // receiver's name..can be changed
 email_json.put("type", "to");

JSONArray email_array = new JSONArray();
 email_array.put(email_json);

JSONObject message_json = new JSONObject();
 message_json.put("html", "");
 message_json.put("text", "Logs: \n" + data);
 message_json.put("subject", "My Android Error Report"); // add your own subject
 message_json.put("from_email", "error@android.com"); // change if your preferred "from email"
 message_json.put("from_name", "Automatic Error Report"); // change it to your preferred name
 message_json.put("to", email_array);

JSONObject jsonObject = new JSONObject();
 jsonObject.put("key", "************5kGY2OhyA");
 jsonObject.put("message", message_json);

return jsonObject.toString();

} catch (JSONException e) {
 e.printStackTrace();
 }

return null;
 }


}


Look closely at method getDataForMandrill() where you have to change receiver’s email address, name, sender’s email address, sender’s name, email subject and you are good to go. You also have to add the key here which we acquired above from mandrillapp.

Now that we have this helper class ready, lets make final changes to MyApp class and make it use this recently created class. Paste following in MyApp:


 mReportsCrashes = this.getClass().getAnnotation(ReportsCrashes.class);
 JsonSender jsonSender = new JsonSender(mReportsCrashes.formUri(), null);
 ACRA.getErrorReporter().setReportSender(jsonSender);

So that finally MyApp looks like this:


@ReportsCrashes(formKey = "", // will not be used
 formUri = "http://mandrillapp.com/api/1.0/messages/send.json"
)

public class MyApp extends Application {
 private ReportsCrashes mReportsCrashes;

@Override
 public void onCreate() {
 super.onCreate();

ACRA.init(this);

mReportsCrashes = this.getClass().getAnnotation(ReportsCrashes.class);
 JsonSender jsonSender = new JsonSender(mReportsCrashes.formUri(), null);
 ACRA.getErrorReporter().setReportSender(jsonSender);

}
}


UPDATE MANIFEST

We need Internet persmission to be able to send reports across. Add following to manifest file.

<uses-permission android:name="android.permission.INTERNET"></uses-permission>

And, add “android:name” attribute to application element like following:

 <application
android:allowBackup="true"
android:name=".MyApp"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">

This is pretty much it. Let’s execute it and get email with crash report.

Error email

Full source code for this application is available at github.

4 thoughts on “Sending crash reports with ACRA over email (using Mandrill)

  1. How do you have this post up in 2015 and the methods are depreciated, can you update it without the depreciated methods… like —- DefaultHttpClient();, HttpPost, NameValuePair, StringEntity, HttpResponse, and EntityUtils…

    Like

Leave a comment