Sunday, November 7, 2010

Adding Crash Report

Cross-posted (in Portuguese) at Technè - Blog de Tecnologia do C.E.S.A.R.

Using only Twitter API for Java ME it is possible to add Crash Report to a Java ME application.

Crash Report is a set of information with details of an issue that made an application stop working.
Operating Systems like Microsoft Windows, Mac OS and Linux distros have this feature.
We browsers like Mozilla Firefox and Google Chrome also do. Android too since version 2.2 .

It is recommended that the application has its own Twitter account. This way all development team can follow application reports.

What should go inside a Crash Report?


  1. Application name and version: MIDlet-Name e Version

  2. Name and value of attributes and variables

  3. Exception that caused the issue

  4. Handset model running the app: microedition.platform



Below is a class that will handle all this info:

public class CrashReport {
private StringBuffer report = new StringBuffer();
private CrashReport (String content) {
report.append(content);
}
public CrashReport (MIDlet midlet, String details, Throwable e) {
if (midlet != null) {
report.append(midlet.getAppProperty("MIDlet-Name"));
report.append(' ');
report.append(midlet.getAppProperty("MIDlet-Version"));
report.append(' ');
}
if (details != null) {
report.append(details).append(' ');
}
if (e != null) {
report.append(e).append(' ');
}
String platform = System.getProperty("microedition.platform");
if (platform != null) {
report.append(platform);
}
}
public String toString () {
return report.toString();
}
public byte [] toByteArray () {
return toString().getBytes();
}
public static CrashReport fromByteArray (byte[] data) {
return new CrashReport(new String(data));
}
}


As soon as we get an unexpected exception we show a dialog stating that there was an error as asking if the details can be sent to the development team. If the user answers no, the report is discarded. If the answer is yes, the app tries to send the message right away.

Below is the class responsible to send the report:


public class CrashReportTwitterSender {
private TweetER tweetEr;
public CrashReportTwitterSender () throws IOException {
// check previous post on how to get a credential instance
UserAccountManager accountManager = UserAccountManager.getInstance(credential);
try {
if (accountManager.verifyCredential()) {
tweetEr = TweetER.getInstance(accountManager);
} else {
throw new IOException("Could not verify credential");
}
} catch (LimitExceededException ex) {
throw new IOException("caused by " + ex.getClass().getName());
}
}
public boolean sendReport (CrashReport report) {
try {
tweetEr.post(new Tweet(report.toString()));
return true;
} catch (Exception ex) {
return false;
}
}
}


If an error happens when the app is trying to send the report we ask the user to save it and send later.
If the user answers no, the report is discarded. If the answer is yes, the app saves the report for future usage.

Below is the class responsible to save CrashReport instances at RecordStore:


public class CrashReportStore {
private RecordStore reports;
public CrashReportStore () throws IOException {
try {
reports = RecordStore.openRecordStore("reports", true);
} catch (RecordStoreException ex) {
throw new IOException("caused by " + ex.getClass().getName());
}
}
public void addReport (CrashReport report) throws IOException {
byte [] data = report.toByteArray();
try {
reports.addRecord(data, 0, data.length);
} catch (RecordStoreException ex) {
throw new IOException("caused by " + ex);
}
}
public boolean hasReports () {
try {
return this.reports.getNumRecords() > 0;
} catch (RecordStoreNotOpenException ex) {
return false;
}
}
public void close() throws IOException {
try {
this.reports.closeRecordStore();
} catch (RecordStoreException ex) {
throw new IOException("caused by " + ex);
}
}
public void sendAllReports (CrashReportTwitterSender sender) throws IOException {
try {
RecordEnumeration re = this.reports.enumerateRecords(null, null, false);
while (re.hasNextElement()) {
sendReportAndDeleteRecord(sender, re.nextRecordId());
}
} catch (Exception ex) {
throw new IOException("caused by " + ex);
}
}
private void sendReportAndDeleteRecord (CrashReportTwitterSender sender, int id)
throws RecordStoreException
{
byte [] data = this.reports.getRecord(id);
CrashReport report = CrashReport.fromByteArray(data);
if (sender.sendReport(report)) {
this.reports.deleteRecord(id);
}
}
}


Next time the application is started it can check if CrashReportStore.hasReports().
If there is none we do not need to bother. If there is some, we ask the user to send the reports now or later.

Below is a sample code to test what we wrote here. It should be pasted at some class that extends MIDlet.


CrashReport report = new CrashReport(this, "crash report test", null);
CrashReportTwitterSender sender = new CrashReportTwitterSender();
if (sender.sendReport(report) == false) {
CrashReportStore store = new CrashReportStore();
store.addReport(report);
store.close();
}


Related topics:

No comments: