Tuesday, February 21, 2012

Crash Reporting Feature to any ANDROID application

AIM: To enable crash Reporting feature to android app

Solution: 

A crash reporter is an feature whose function is to report crash data to a third party, usually to the party responsible for the crashed program. Crash reports often include data such as stack traces, type of crash, and version of software. This information helps software developers to diagnose and fix the underlying problem causing the crash.

The Android and iOS operating systems also have built in crash reporting functionality.
There are third party tools that also provide it, like

BugSense :
BugSense is made for mobile developers. Get the context of the errors, track errors in specific app version or filter errors by device. There was Internet connectivity at the time of the crash? BugSense collects all the information the mobile developer needs. 

ACRA (Application Crash Report for Android).

ACRA is a library enabling Android Application to automatically post their crash reports to a GoogleDoc form. It is targetted to android applications developers to help them get data from their applications when they crash or behave erroneously.  

However you can add this feature with the following code block.

---------------------------------------------------------------------------------
/**
 * Crash Error Reporter
 *
 * Captures any Uncaught exception to capture the conditions that caused the event.
 * The event will be written to the Log file and can be retrieved through LogCat.
 * In addition, the report will attempt to send an e-mail to <string name="CrashErrorReport_MailTo">xyz@GMail.com</string>
 * through the default mail application
 *  *
 */


public class CrashErrorReporter implements Thread.UncaughtExceptionHandler {
   
    private static final String LOG_TAG = "CrashErrorReporter";

    private Thread.UncaughtExceptionHandler mDfltExceptionHandler;
    private static CrashErrorReporter S_mInstance;
    private static Context mCurContext;

    // Even though these should be private variables,
    // they are public to avoid creating getters and setters
    String mPkg_VersionName;
    String mPkg_PackageName;
    String mCtx_FilePath;
   
    String mPkg_OSBld_PhoneModel;
    String mPkg_OSBld_AndroidVersion;
    String mPkg_OSBld_Board;
    String mPkg_OSBld_Brand;
    String mPkg_OSBld_Device;
    String mPkg_OSBld_Display;
    String mPkg_OSBld_FingerPrint;
    String mPkg_OSBld_Host;
    String mPkg_OSBld_ID;
    String mPkg_OSBld_Manufacturer;
    String mPkg_OSBld_Model;
    String mPkg_OSBld_Product;
    String mPkg_OSBld_Tags;
    long   mPkg_OSBld_Time;
    String mPkg_OSBld_Type;
    String mPkg_OSBld_User;

    /**
     * Manages the uncaught exception.
     */
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        Log.d(LOG_TAG, "@Override uncaughtException");

        Date CurDate = new Date();

        String Report = "Error Report collected on : " + CurDate.toString() + "\n\n";
        Report += "Environment Details : \n";
        Report += "===================== \n";
        Report += CreateInformationString();
       
        Report += "Stack : \n";
        Report += "======= \n";
        final Writer result = new StringWriter();
        final PrintWriter printWriter = new PrintWriter(result);
        e.printStackTrace(printWriter);
        String stacktrace = result.toString();
        Report += stacktrace  + "\n";

        // If the exception was thrown in a background thread inside
        // AsyncTask, then the actual exception can be found with getCause
        Throwable cause = e.getCause();
        while (cause != null) {
            Report += "Cause : \n";
            Report += "======= \n";
            cause.printStackTrace( printWriter );
            Report += result.toString();
            cause = cause.getCause();
        }
        printWriter.close();
        Report += "**** End of current Report ***";
        SaveAsFile(Report);
       
        // Try and send out the report now before calling the default handler
        CheckCrashErrorAndSendMail(mCurContext);
        mDfltExceptionHandler.uncaughtException(t, e);
    }
   
    /**
     * Capture the newly created instance for singleton class management
     */
    public static CrashErrorReporter getInstance() {
        if ( S_mInstance == null )
            S_mInstance = new CrashErrorReporter();
        return S_mInstance;
    }

    public void Init( Context context ) {
        mDfltExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler( this );
        mCurContext = context;
    }
   
    /**
     * Obtains the available internal memory size
     * @return long  - integer with memory size available
     */
    public long getAvailableInternalMemorySize() {
        File   path = Environment.getDataDirectory();
        StatFs stat = new StatFs(path.getPath());
       
        // place in stack variables for debugging purposes.
        long blockSize       = stat.getBlockSize();
        long availableBlocks = stat.getAvailableBlocks();
       
        return( availableBlocks * blockSize );
    }

    /**
     * Obtains the Total internal memory size
     * @return long - integer with total memory size
     */
    public long getTotalInternalMemorySize() {
        File path = Environment.getDataDirectory();
        StatFs stat = new StatFs(path.getPath());
       
        // place in stack variables for debugging purposes.
        long blockSize = stat.getBlockSize();
        long totalBlocks = stat.getBlockCount();
       
        return( totalBlocks * blockSize );
    }
   
    void CollectPackageInformation( Context context )     {
        Log.d(LOG_TAG, "@CollectPackageInformation");

        try {
            PackageManager pm = context.getPackageManager();
            PackageInfo    pi = pm.getPackageInfo(context.getPackageName(), 0);
           
            mPkg_VersionName        = pi.versionName;
            mPkg_PackageName        = pi.packageName;
            mCtx_FilePath           = context.getFilesDir().getAbsolutePath();
           
            mPkg_OSBld_PhoneModel         = android.os.Build.MODEL;
            mPkg_OSBld_AndroidVersion     = android.os.Build.VERSION.RELEASE;
            mPkg_OSBld_Board              = android.os.Build.BOARD;
            mPkg_OSBld_Brand             = android.os.Build.BRAND;
            mPkg_OSBld_Device             = android.os.Build.DEVICE;
            mPkg_OSBld_Display             = android.os.Build.DISPLAY;
            mPkg_OSBld_FingerPrint         = android.os.Build.FINGERPRINT;
            mPkg_OSBld_Host             = android.os.Build.HOST;
            mPkg_OSBld_ID                 = android.os.Build.ID;
            mPkg_OSBld_Model             = android.os.Build.MODEL;
            mPkg_OSBld_Product             = android.os.Build.PRODUCT;
            mPkg_OSBld_Tags             = android.os.Build.TAGS;
            mPkg_OSBld_Time             = android.os.Build.TIME;
            mPkg_OSBld_Type             = android.os.Build.TYPE;
            mPkg_OSBld_User             = android.os.Build.USER;
        } catch( Exception e ) {
            Log.e(LOG_TAG, "!Error CollectPackageInformation: " + e.getMessage());
            // e.printStackTrace()
        }
    }
   
    /**
     * Assemble the package information in a string format
     * @return String - Package information collected
     */
    private String CreateInformationString() {
        CollectPackageInformation( mCurContext );

        String ReturnVal = "";
        ReturnVal  = "  Version  : " + mPkg_VersionName + "\n";
        ReturnVal += "  Package  : " + mPkg_PackageName + "\n";
        ReturnVal += "  FilePath : " + mCtx_FilePath    + "\n\n";
        ReturnVal += "  Package Data \n";
        ReturnVal += "      Phone Model : " + mPkg_OSBld_PhoneModel     + "\n";
        ReturnVal += "      Android Ver : " + mPkg_OSBld_AndroidVersion + "\n";
        ReturnVal += "      Board       : " + mPkg_OSBld_Board          + "\n";
        ReturnVal += "      Brand       : " + mPkg_OSBld_Brand          + "\n";
        ReturnVal += "      Device      : " + mPkg_OSBld_Device         + "\n";
        ReturnVal += "      Display     : " + mPkg_OSBld_Display        + "\n";
        ReturnVal += "      Finger Print: " + mPkg_OSBld_FingerPrint    + "\n";
        ReturnVal += "      Host        : " + mPkg_OSBld_Host           + "\n";
        ReturnVal += "      ID          : " + mPkg_OSBld_ID             + "\n";
        ReturnVal += "      Model       : " + mPkg_OSBld_Model          + "\n";
        ReturnVal += "      Product     : " + mPkg_OSBld_Product        + "\n";
        ReturnVal += "      Tags        : " + mPkg_OSBld_Tags           + "\n";
        ReturnVal += "      Time        : " + mPkg_OSBld_Time           + "\n";
        ReturnVal += "      Type        : " + mPkg_OSBld_Type           + "\n";
        ReturnVal += "      User        : " + mPkg_OSBld_User           + "\n";
        ReturnVal += "  Internal Memory\n";
        ReturnVal += "      Total    : " + (getTotalInternalMemorySize()     /1024) + "k\n";
        ReturnVal += "      Available: " + (getAvailableInternalMemorySize() /1024) + "k\n\n";

        return ReturnVal;
    }
   
    /**
     *Saves the Crash Report to a File with the name stack-timestamp.stacktrace in SD card
     *
           */
    private void SaveAsFile( String ErrorContent ) {
        try    {
            long timestamp = System.currentTimeMillis();
            String ErrFileName = "stack-" + timestamp + ".stacktrace";
           
            FileOutputStream trace = mCurContext.openFileOutput( ErrFileName, Context.MODE_PRIVATE);
            trace.write(ErrorContent.getBytes());
            trace.flush();
            trace.close();
            Log.e(LOG_TAG, "!Error Report: " + ErrFileName + "\n" + ErrorContent);
        } catch( Exception e ) {
            Log.e(LOG_TAG, "!Error SaveAsFile: " + e.getMessage());
        }
    }

    /**
     * Returns an array containing the names of available crash report files.
     *
     * @return an array containing the names of available crash report files.
     */
    private String[] GetCrashErrorFileList() {
        File dir = mCurContext.getFilesDir();

        Log.d(LOG_TAG, "Looking for error files in " + dir.getAbsolutePath());

        // Filter for "stack trace" files
        FilenameFilter filter = new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return name.endsWith(".stacktrace");
            }
        };
        return dir.list(filter);
    }
   
    /**
     * Checks to see if there are any crash reports to send and sends them if they exist
     * Once finished with the report, then the report file is deleted from the system
     *
     * @param _context
     */
    public void CheckCrashErrorAndSendMail(Context _context )
    {
        try {
            if( null == mCtx_FilePath ) {
                mCtx_FilePath = _context.getFilesDir().getAbsolutePath();
            }
            String[] reportFilesList = GetCrashErrorFileList();
            TreeSet<String> sortedFiles = new TreeSet<String>();
            sortedFiles.addAll(Arrays.asList(reportFilesList));
            if((null != reportFilesList) && (0 < reportFilesList.length)) {
               
                String line;
                String WholeErrorText = "";
                int curIndex = 0;
                final int MaxSendMail = 5;
               
                for ( String curString : sortedFiles )
                {
                    if ( curIndex++ <= MaxSendMail )
                    {
                        WholeErrorText+="New Trace collected :\n";
                        WholeErrorText+="=====================\n ";
                        String filePath = mCtx_FilePath + "/" + curString;
                        BufferedReader input = new BufferedReader(new FileReader(filePath));
                        while (( line = input.readLine()) != null) {
                            WholeErrorText += line + "\n";
                        }
                        input.close();
                    }

                    // DELETE FILES !!!!
                    File curFile = new File( mCtx_FilePath + "/" + curString );
                    curFile.delete();
                }
                SendCrashErrorMail( _context , WholeErrorText,_context.getString(R.string.CrashErrorReport_MailTo) );
            }
        } catch( Exception e ) {
            Log.e(LOG_TAG, "!Error CheckCrashErrorAndSendMail: " + e.getMessage());
            // e.printStackTrace();
        }
    }
   
    /**
     * Send out the crash error report via e-mail
     *
     * @param _context
     * @param ErrorContent
     */
    private void SendCrashErrorMail( Context _context, String ErrorContent,String mailTo)    {
        Log.d(LOG_TAG, "SendCrashErrorMail: " + _context.getString(R.string.CrashErrorReport_MailTo));
       
        //Toast.makeText(_context, _context.getString(R.string.CrashErrorReport_ToastText), Toast.LENGTH_LONG).show();

        Intent sendIntent = new Intent(Intent.ACTION_SEND);
        sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        sendIntent.putExtra(Intent.EXTRA_EMAIL,    new String[]{_context.getString(R.string.CrashErrorReport_MailTo)});
        sendIntent.putExtra(Intent.EXTRA_SUBJECT, _context.getString(R.string.CrashErrorReport_MailSubject));
        sendIntent.putExtra(Intent.EXTRA_TEXT, ErrorContent + "\n");
        sendIntent.setType("message/rfc822");
        _context.startActivity( sendIntent );
    }
   
    /**
     * Send out the crash report via HTTP connection
     * NOTE: This is currently not used, but here for future implementation
     * @param _context
     * @param ErrorContent
     *
     * The below code would need to be placed on an server to receive HTTP transmissions.
     * upload_server.php
     * <?php       
     *   if ( $_POST['stacktrace'] == "" || $_POST['package_version'] == "" || $_POST['package_name'] == "" ) {       
     *           die("This script is used to collect field test crash stacktraces. No personal information is transmitted, collected or stored.<br/>For more information, please contact <a href='mailto:support@nullwire.com'>email@domain.com</a>");       
     *  }       
     *   $random = rand(1000,9999);       
     *   $version = $_POST['package_version'];       
     *   $package = $_POST['package_name'];       
     *   $handle = fopen($package."-trace-".$version."-".time()."-".$random, "w+");       
     *   fwrite($handle, $_POST['stacktrace']);       
     *   fclose($handle);       
      *  
     *    ?>
     *
     */
    @SuppressWarnings("unused")
    private void SendCrashErrorHTTP( Context _context, String ErrorContent ) {
        Log.d(LOG_TAG, "SendCrashErrorHTTP: " + _context.getString(R.string.CrashErrorReport_URL));
        Toast.makeText(_context, _context.getString(R.string.CrashErrorReport_ToastText), Toast.LENGTH_LONG).show();

        String sError = null;
        // Transmit stack trace with POST request
        DefaultHttpClient httpClient = new DefaultHttpClient();
        HttpPost httpPost = new HttpPost(_context.getString(R.string.CrashErrorReport_URL));
        List <NameValuePair> nvps = new ArrayList <NameValuePair>(); 
        nvps.add(new BasicNameValuePair("package_name", mPkg_PackageName));
        nvps.add(new BasicNameValuePair("package_version", mPkg_VersionName));
        nvps.add(new BasicNameValuePair("phone_model", mPkg_OSBld_PhoneModel));
        nvps.add(new BasicNameValuePair("android_version", mPkg_OSBld_AndroidVersion));
        nvps.add(new BasicNameValuePair("stacktrace", ErrorContent));
       
        try {
             httpPost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
             // We don't care about the response, so we just hope it went well and on with it
            httpClient.execute(httpPost);
        } catch (UnsupportedEncodingException e) {
            sError = e.getMessage();
        } catch (ClientProtocolException e) {
            sError = e.getMessage();
        } catch (IOException e) {
            sError = e.getMessage();
        } finally {
            sError = "Unknown Error condition";
        }
        if( null != sError ) {
            Log.e(LOG_TAG, "!Error SendCrashErrorHTTP: " + sError);
        }
    }
   
// End Class   
}

------------------------------------------------------

You can add this feature by intiating this code in any activity like Loading,SplashScreen.

// Register the Crash Reporting feature
------------------------------------------
        // Initialize ErrorReporter with all required data
        CrashErrorReporter mCrashErrorReporter = CrashErrorReporter.getInstance();
       
       // Activate the ErrorReporter
        mCrashErrorReporter.Init(getApplicationContext());
        mCrashErrorReporter.CheckCrashErrorAndSendMail(getApplicationContext());

-------------------------------------------------

This just works amazing...

Let me know if any problems you have.

21 comments:

  1. That is really attention-grabbing, You're a very professional blogger. I've joined your feed and stay up
    for in quest of more of your wonderful post. Also, I've shared your web site in my social networks

    Here is my web blog - metabolic rate calculator

    ReplyDelete
  2. Very nice article. I absolutely love this website. Thanks!



    Stop by my web blog US real estate agents

    ReplyDelete
  3. Hi mates, its impressive article regarding tutoringand completely defined, keep
    it up all the time.

    Stop by my site; definition essay examples

    ReplyDelete
  4. excellent issues altogether, you just won a brand new reader.
    What would you recommend in regards to your put up that you simply made some days in the past?
    Any certain?

    Review my web site :: US real estate agents directory

    ReplyDelete
  5. What's up it's me, I am also visiting this website daily, this website is really good and the users are really sharing
    pleasant thoughts.

    Visit my site - poetry research paper

    ReplyDelete
  6. Hello colleagues, how is the whole thing, and what you desire to say about this
    paragraph, in my view its in fact awesome designed for me.


    Feel free to surf to my web-site: us business directories

    ReplyDelete
  7. Please let me know if you're looking for a author for your weblog. You have some really great posts and I believe I would be a good asset. If you ever want to take some of the load off, I'd really like to
    write some material for your blog in exchange
    for a link back to mine. Please shoot me an e-mail if interested.
    Kudos!

    my page: term paper formats

    ReplyDelete
  8. I love reading a post that will make people
    think. Also, many thanks for allowing me to comment!


    Look into my web-site top business directory sites

    ReplyDelete
  9. Everything is very open with a very clear description of
    the challenges. It was definitely informative.

    Your site is extremely helpful. Many thanks for
    sharing!

    my blog post; movie discussion guides

    ReplyDelete
  10. This page certainly has all of the information I wanted about this subject and didn't know who to ask.

    my weblog: cartoon t shirts for adults

    ReplyDelete
  11. Post writing is also a fun, if you know after
    that you can write if not it is complex to write.



    Also visit my web site: cheap cartoon t-shirts

    ReplyDelete
  12. It's in reality a nice and helpful piece of information. I'm satisfied that you just shared this useful info with us.
    Please keep us up to date like this. Thank you for sharing.


    Feel free to visit my web blog ... custom college essays

    ReplyDelete
  13. Thanks for the auspicious writeup. It actually was a leisure account it.
    Glance advanced to far added agreeable from you! However, how could we be in contact?



    My web blog; buy old computers charlotte nc

    ReplyDelete
  14. whoah this blog is magnificent i love reading your articles.

    Stay up the good work! You know, lots of persons are searching
    round for this info, you could help them greatly.

    Also visit my webpage - local company directory

    ReplyDelete
  15. Excelent!
    Thanks. It saved my day!

    ReplyDelete
  16. Excellent Work..Helps Me a Lot. But when the app crashes its ask for actions i am choosing by email . ,instead of asking any actions can we directly sends the email when app crashes without asking any actions like send by email or etc .., Waiting For Your response .Thanks in Advance

    ReplyDelete
  17. Thanks for the detailed information!

    There are two other products that do a good job, Gryphonet and Crashlytics.
    The advantage of Gryphonet is that it's automatic and reproduction of malfunction is very easy.

    ReplyDelete
  18. Excellent Post !!
    Thanks.

    ReplyDelete
  19. hi thanks for this wonderful idea to achieve success to my app crash reporting problem.i really like this post.

    ReplyDelete

Android Developers Blog

Ram's shared items